本文是JS高级入门的配套试题,题目解析的部份内容将引用JS高级入门
。面试
题目一(this指针)
function logName(){ console.log(this.name); } function doFun1(fn){ fn(); } function doFun2(o){ o.logName(); } var obj = { name: "LiLei", logName: logName }; var name = "HanMeiMei"; doFun1(obj.logName); doFun2(obj);
题目首先定义了三个函数:
【】logName:打印调用者(即谁去调用它,一般是一个对象)的name属性。
【】doFun1:接收一个函数,并直接运行这个函数。
【】doFun2:接收一个对象,并由这个对象去调用logName函数。
然后定义了两个变量:
【】obj是一个对象,里面定义了name(假设另命名为_name)的字符串变量。定义了logName(假设另起名字为_logName),指向外部的logName函数。
【】name是一个字符串变量。
最后分别对两个函数进行调用,
【】doFun1(obj.logName)。segmentfault
向函数传递了obj对象内部的_logName,而_logName是指向logName的。因此实际上doFun1接收的是指向logName函数的变量。即等价于doFun1(logName)。数组
而在doFun1内部是直接执行logName的。没有明确的调用者。则这时等价于由window对象去调用,等价于window.logName。闭包
而再因为全部在全局做用域(注意仅仅是全局做用域)中定义的变量都是window对象下的变量。均可以经过window对象进行访问。
因此这时访问到的就是name。app
因此输出的是HanMeiMei。dom
【】doFun2(obj)。函数
传递给doFun2的是obj的地址值,即doFun2中的o指向的就是obj,等价于obj。this
o.logName是由o去调用logName。至关于obj.logName。线程
因此找到的是obj内部的name(即咱们假定的_name)。指针
因此打印出的是LiLei。
HanMeiMei LiLei
题目二(this指针的修改apply)
function fun(somthing) { console.log(this.name, somthing); } function bindFun(fn, obj) { return function () { return fn.apply(obj, arguments); } } var obj = { name: "LiLei" }; var bar = bindFun(fun, obj); var b = bar("HanMeiMei"); console.log(b);
本题的主要考点是this的相关函数apply。
首先定义了三个函数:
【】fun函数:接收变量,而且打印这个函数的调用者的name值,以及形参something。
【】bindFun函数:接收两个参数,分别是函数以及对象。bindFun没有逻辑操做。只是返回了一个匿名函数(咱们假定为_fun)
【】_fun函数:一样没有操做,直接返回一个已经经过apply修改了this的函数的执行结果
。(即直接返回一个函数执行结果值,而这个函数的this的值是已经被修改过的)
然后定义了三个变量:
【】obj是一个对象,里面只定义了name字符串变量。
【】bar是一个变量,这个变量的值为bindFun函数执行的结果值。
【】b是一个变量,值为bar函数执行的结果值。
整段代码中,除了定义,一共只执行了3句逻辑语句:
【】在bindFun函数的执行,传递了两个函数,分别是fun函数,obj对象。bindFun函数执行结果是返回一个函数_fun。因此bar指向_fun函数。
【】bar函数的调用,即至关于_fun函数的调用。
_fun函数的返回fn.apply(obj, arguments)的运行值;
其中这里的fn是bindFun函数接受的第一个参数fun,即返回的是fun函数。
而这个fun函数进行了apply的函数调用,修改了函数中this的值。
this指向为bindFun函数的第二个参数值,即外部的obj变量。
apply函数还接受了第二个参数arguments,即_fun函数的参数数组。即bar函数的参数数组【'HanMeiMei'】。做为fun函数的参数。
apply函数的做用是立刻执行调用者函数,即立刻执行bar(即_fun,即fun)函数。
fun函数执行是打印this.name,和参数something。this指向obj,因此this.name为LiLei。而something值为【'HanMeiMei'】
因此打印了,LiLeiHanMeiMei。而且没有返回值。
【】b是bar函数的返回值,然而bar函数并无返回东西,因此是undefined。因此console.log(b)打印的值是undefined。
绕了一圈,整个过程至关于执行。fun.call(obj,'HanMeiMei');
LiLeiHanMeiMei undefined
题目三(自执行函数)
function logName() { console.log(this); console.log(this.name); } var name = "XiaoMing"; var LiLei = { name: "LiLei", logName: logName }; var HanMeiMei = { name: "HanMeiMei" }; (HanMeiMei.logName = LiLei.logName)();
这题相对简单一些。只考到自执行函数的定义。并无借助他的特性作代码隔离。只是想考一下是否已经掌握了自执行函数的定义。
首先定义了一个函数:
【】logName,打印出调用者(即this指针的指向),已经调用者的name属性值
然后定义了三个变量:
【】name:字符串变量,因为全局的,因此至关于window对象的属性值。
【】LiLei:定义了对象,该对象包含name值,以及logName(咱们假定LiLei的logName从新命名为_logName)。指向外部的logName函数
【】HanMeiMei:定义了对象,该对象只包含name值。
最终只执行了一条语句,两个步骤。
分别是赋值语句:
为HanMeiMei对象添加一个参数logName,赋值为LiLei的_logName。即指向外部的logName。这不是重点,重点是赋值语句的结果是什么?
是所赋的值,即赋值语句的结果是LiLei.logName,即HanMeiMei.logName = LiLei.logName终止获得的是logName。
而后对这个函数进行调用。即logName(),这时this没有发生改变,仍是window,因此输出是打印window对象。已经window.name即XiaoMing。
XiaoMing
题目四(声明提早)
test(); var test = 0; function test() { console.log(1); } test(); test = function () { console.log(2); }; test();
声明提早的题,把背后的代码执行顺序理顺就好。
首先将声明都放到代码的最上面:
var test;//定义变量
function test(){console.log(1)}//定义函数
而后执行的操做:
test();//函数调用操做
test = 0;//赋值操做
test();//函数调用操做
test = function(){console.log(2)}//赋值操做,将test赋值为函数
test();//函数调用操做
因此上述代码等价于(声明提早,先定义,后执行):
var test;
function test(){console.log(1)}
test();//调用函数,输出1
test = 0;
test();//此时test为0,不是函数,将报错test is not a function
test = function(){console.log(2)}//因为JS报错,后面的代码将不被运行
test();//因为JS报错,后面的代码将不被运行
综合来讲,这里设置了三个考点:
声明提早。
函数的定义与函数赋值的区别,function xx为函数定义,将总体上移。
JS报错后,后面的代码将不被运行。
1 Uncaught TypeError: test is not a function
题目五(基本类型与引用类型)
var name = "LiLei"; var people = {name: "HanMeiMei"}; function test1(name) { name = "LaoWang"; } function test2(obj) { obj.name = "LaoWang"; } test1(name); test2(people); console.log("name " + name); console.log("name " + people.name);
题目首先定义了两个函数:
【】test1:接受一个参数,并修改该参数的值。
【】test2:接收一个对象,并修改该对象的属性的值。
然后定义了两个变量:
【】name是一个字符串参数,值为'LiLei'
【】people是一个对象,包含一个name属性。值为'HanMeiMei'
而后分别对两个函数进行调用:
test1(name):即将name做为参数,调用test1函数。
这时函数的内部将产生一个新的参数name(记做_name),它的值等于外部的name的值(LiLei);
test1函数将_name的值修改成'LaoWang',可是因为_name和name是两份独立的变量。因此name的值不受改变。
test2(people):将people做为参数,调用test1函数。
这时函数的内部将产生一个新的参数obj,它的值等于外部的people的值;
而people是引用类型,其值为对象{name:"HanMeiMei"}的地址值
。因此obj也为对象{name:"HanMeiMei"}的地址值。
test2对对象{name:"HanMeiMei"}的name属性进行修改,改成'LaoWang'
因此obj和people的name值都发生了改变。
这里涉及到两个知识点
基本数据类型是按值访问的,即该变量就存在了实际值。而引用数据类型保存的是则是对实际值的引用(即指向实际值的指针)。
函数形参(即在函数中实际使用的值,如test函数里面的name)和参数的实参(即往调用函数时调用的参数,如test(name)中的name)的值相同,但并非"同一个值"(在内存中的地址是不一样的,至关于var a = b =0;)。
在函数参数的传递,是经过按值传递的。
LiLei LaoWang
题目六(JS线程与闭包)
执行的结果是什么,分别在什么时间输出 for (var i = 1; i < 5; i++) { setTimeout(function () { console.log(i); }, i * 1000); }
JS线程的规则:程序将先把主逻辑的内容作完,再去读取消息列表,调用消息列表中的回调函数
在这里主逻辑为一个for循环,从i为1到i为4循环执行4次for循环的内容。
for循环内,调用setTimeout函数,并设置第二个参数的值为i。注意这里是对setTimeout函数直接进行调用。因此参数中i的值是随for循环改变的。因此至关于执行
setTimeout(function () {doSomething}, 1000);
setTimeout(function () {doSomething}, 2000);
setTimeout(function () {doSomething}, 3000);
setTimeout(function () {doSomething}, 4000);
而setTimeout的做用是往消息队列中存放一个回调函数,并在特定时间间隔后执行它。因此该回调函数会在for循环以后完成。
for循环执行完时,i的值为5。(由于5<5不成立,结束循环)。因此调用回调函数function(){console.log(i)}时,i的值为5。因此输出为5。
闭包是由于回调函数引用到了for循环的i,回调函数没执行完,i不能被回收。因此仍是能访问到。
输出4次,每一秒输出一个5
题目七(做用域陷阱)
function logName(){ console.log(name); } function test () { var name = 3; logName(); } var name = 2; test();
一句话能够解释完:做用域的层级关系与函数定义时所处的层级关系相同
注意是,函数定义时的层级关系,而不是调用时的层级关系。
在这里,logName函数,test函数以及外部的name变量(值为2)处于同一个层级。
因此调用logName时,找到的是外部的name变量。
因此打印出2
2
题目八(隐式闭包)
function logNum(num) { console.log("num " + num); } for (var i = 0; i < 2; i++) { var fun = logNum.bind(null, i); setTimeout(fun, 100); }
这道题和第六题很像,可是运行结果并不同。
缘由是bind函数产生了隐式闭包。
为何bind函数能修改this指针?底层实现笔者不知道,可是咱们能够利用闭包的特性经过自执行函数来模拟bind函数。
bind(obj,elem0,elem1,elem2...)函数至关于
(function(_obj,_elem0,_elem1,_elem2...){ return function(){//这个函数就是调用bind函数的函数 doSomething; //将this.替换成_obj. //这里将可能使用到_elem0,_elem1,_elem2参数。 } })(obj,elem0,elem1,elem2...)
经过自执行函数返回一个函数,造成一个闭包,内部函数调用的参数是自执行函数的参数,而不是外部的元素。
根据前面第五题的解析函数形参(即在函数中实际使用的值,如test函数里面的name)和参数的实参(即往调用函数时调用的参数,如test(name)中的name)的值相同,但并非"同一个值"(在内存中的地址是不一样的,至关于var a = b =0;)。
doSomething中使用到的参数是自执行函数的形参(_elemX),而不是外部的实参elem。
因此外部的elem的变化对doSomething没有影响。
根据解析,对题目进行改造,至关于
function logNum(num) { console.log("num " + num); } for (var i = 0; i < 2; i++) { var fun = (function(_i){ return function(){ console.log(_i); } })(i); setTimeout(fun, 100); }
而自执行函数是定义完立刻执行的。因此拿到的值_i也是随着for循环改变的。
因此输出0 1
利用该特性还能够解决常见的面试题:经过原生JS返回所点击元素索引值。
var domList = document.getElementsByTagName('div'); for (var i = 0, length = domList.length; i < length; i++) { var item = domList[ i ]; (function(_item, _i) { _item.addEventListener('click', function() { console.log(_i); }) })(item, i) }
0 1