见过这道题吗?
<!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