一道经典JS面试题

见过这道题吗?

<!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
posted @ 2017-09-13 16:12  indigojh  阅读(160)  评论(0编辑  收藏  举报