一道经典JS面试题

时间:2022-08-19 19:31:36

见过这道题吗?

<!DOCTYPE html>
<html>
<head>
<meta name="description" content="iMagic">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<h2>点击以下三个li,输出分别是什么?</h2>
<ul>
<li>Item 0</li>
<li>Item 1</li>
<li>Item 2</li>
</ul>
<script>
var lis = document.getElementsByTagName('li');

for (var i = 0; i < lis.length; i++) {
lis[i].onclick
= function (event) {
console.log(
'你点击了 ' + i);
}
}
</script>
</body>
</html>

第一个问题,点击三个li,console输出结果是什么?(答错扣30分)

# 聪明的你这时点击了三个li

"你点击了 3"
"你点击了 3"
"你点击了 3"

(⊙⊙!) 

第二个问题,为什么输出总是“3”呢?(答错扣30分,可以考虑提前结束面试)

我们祭出console.log大法:

 var lis = document.getElementsByTagName('li');

for (var i = 0; i
< lis.length; i++) {
console.log('添加第' + i + '个onclick listener');

lis[i].onclick
= function (event) {
console.log(
'只有点击时才进入onclick这个function, 这时i已经变成了' + i);
console.log('你点击了 ' + i);
}

console.log('i这时变成了' + i);
}

console.log('i在for循环结束后变成了' + i);
"添加第0个onclick listener"
"i这时变成了0"

"添加第1个onclick listener"
"i这时变成了1"

"添加第2个onclick listener"
"i这时变成了2"

"i在for循环结束后变成了3"

# 聪明的你这时点击了一个li

"只有点击时才进入onclick这个function, 这时i已经变成了3"
"你点击了 3"

第三个问题,如何修复这段代码,确保在点击Item 0, 1, 2的时候分别输出0, 1, 2?(答不出来扣40分,结束面试,每给出一种解法加30分)

第一种解法:利用closure对每一个 onclick event handler 建立一个其专属的 function,并在建立每个function的时候保存住 i 的值:

 var lis = document.getElementsByTagName('li');

for (var i = 0; i
< lis.length; i++) {

lis[i].onclick
= (function (invokedi) {
console.log('现在建立对于Item' + invokedi + '的event handler');
return function uniqEventHandlerFori(event){
console.log(
'invokedi是在建立event handler时传入的,它的值是' +
invokedi);
console.log('你点击了 ' + invokedi);
}
}(i))

}

结果:

"现在建立对于Item0的event handler"
"现在建立对于Item1的event handler"
"现在建立对于Item2的event handler"

# 聪明的你这时点击了三个li

"invokedi是在建立event handler时传入的,它的值是0"
"你点击了 0"

"invokedi是在建立event handler时传入的,它的值是1"
"你点击了 1"

"invokedi是在建立event handler时传入的,它的值是2"
"你点击了 2"

 

第二种解法利用 Function.prototype.bind(thisArg, params...) 对每一个 onclick event handler 建立一个其专属的 function,并在建立每个function的时候保存住 i 的值:

var lis = document.getElementsByTagName('li');

for (var i = 0; i
< lis.length; i++) {

lis[i].onclick
= function handlerToBind (event){
console.log(
'function handlerToBind(){...}.bind(i) --
> ' +
' 建立了一个新的function,并且在这个函数里面,' +
'this === 运行bind(i)时i的值 === ' + this);

console.log('你点击了 ' + this);
}.bind(i);

}

结果:# 聪明的你这时点击了三个li


"function handlerToBind(){...}.bind(i) --> 建立了一个新的function,并且在这个函数里面,this === 运行bind(i)时i的值 === 0"
"你点击了 0"

"function handlerToBind(){...}.bind(i) --> 建立了一个新的function,并且在这个函数里面,this === 运行bind(i)时i的值 === 1"
"你点击了 1"

"function handlerToBind(){...}.bind(i) --> 建立了一个新的function,并且在这个函数里面,this === 运行bind(i)时i的值 === 2"
"你点击了
2"

第三种解法event.target 拿到实际被点击的 li,然后利用 Array.prototype.indexOf(item) 去查找它的index。
var lis = document.getElementsByTagName('li');

console.log(
'通过document.getElementsByTagName返回的lis' +
'并没有返回真正的Array, 只是一个Array-like object. ' +
'我们需要把它构建成一个真正的Array才能在待会用indexOf方法. ' );

var lisArray = Array.prototype.slice.call(lis);

console.log('现在你可以愉快的使用lisArray.indexOf(item)了');

for (var i = 0; i
< lis.length; i++) {

lis[i].onclick
= function handlerToBind (event){

console.log(
'event.target 就是被点击的 DOM Element: ' +
event.target.innerHTML +
'. 它存在于lis和lisArray中');

console.log('你点击了 ' + lisArray.indexOf(event.target));
};

}

 

结果:

 "通过document.getElementsByTagName返回的lis并没有返回真正的Array, 只是一个Array-like object. 我们需要把它构建成一个真正的Array才能在待会用indexOf方法. "

"现在你可以愉快的使用lisArray.indexOf(item)了"

# 聪明的你这时点击了三个li

"event.target 就是被点击的 DOM Element: Item 0. 它存在于lis和lisArray中"
"你点击了 0"

"event.target 就是被点击的 DOM Element: Item 1. 它存在于lis和lisArray中"
"你点击了 1"

"event.target 就是被点击的 DOM Element: Item 2. 它存在于lis和lisArray中"
"你点击了 2"


(重点)第四种解法:使用ES6的 let,把 i 限定在block level里面 (单独给出此解法加10分,如果同时给出第一或第二种解法,此解法加30分)
 var lis = document.getElementsByTagName('li');

for (let i = 0; i
< lis.length; i++) {
lis[i].onclick
= function (event) {
console.log('你点击了 ' + i);
}
}

结果:

# 聪明的你这时点击了三个li

"你点击了 0"
"你点击了 1"
"你点击了 2"
参考:https://zhuanlan.zhihu.com/p/24024766