本文是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)。
向函数传递了obj对象内部的_logName,而_logName是指向logName的。所以实际上doFun1接收的是指向logName函数的变量。即等价于doFun1(logName)。
而在doFun1内部是直接执行logName的。没有明确的调用者。则这时等价于由window对象去调用,等价于window.logName。
而再由于所有在全局作用域(注意仅仅是全局作用域)中定义的变量都是window对象下的变量。都可以通过window对象进行访问。
所以这时访问到的就是name。所以输出的是HanMeiMei。
【】doFun2(obj)。
传递给doFun2的是obj的地址值,即doFun2中的o指向的就是obj,等价于obj。
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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。