一道面试题引发的思考

题目:

在不用jQuery的情况下,ul中点击任意 li,弹出它在该ul中的位置,即第几个 li
这道题目大致有两种解法:

第一种解法思考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var liArr = document.getElementsByTagName('li');
for(var i = 0,length = liArr.length; i<length; i++){
(function(i){
liArr[i].onclick = function(){
alert(i+1)
}
})(i)
/**liArr[i].index = i;
liArr[i].onclick = function(){
alert(this.index)
}**/
/**liArr[i].onclick = (function(i){
return function(){
alert(i+1)
}**/
})(i)
}

这种解法主要应用了闭包相关的知识,给每个 li都绑定了一个事件。
如果不用闭包,你会发现,每次点击弹出的都是 i 的最后一个值。
为什么?因为onclick绑定的函数是异步事件,不会立即执行,会等到点击之后才会触发。
用户点击 li,会执行函数体alert(i),i 在哪儿?JS会顺着作用域链查找,找到 i 之后发现已经被for循环了一遍了,值已经改变为了最后一个值。所以想要解决这个问题,ES5里可以通过把onclick事件通过闭包(关于闭包可以查看我博客中的JavaScript闭包一文)的形式赋值或者给每个 li绑定一个index,点击的时候找到这个index即可;ES6可以直接用let 关键字来指定 i 值。

第二种解法思考:

1
2
3
4
5
6
var liArr = document.getElementsByTagName('li');
for(var i = 0,length = liArr.length; i<length; i++){
liArr[i].onclick = function(){
alert([].indexOf.call(this.parentNode.children,this))
}
}

其实这个问题换一种思路看其实挺简单的, ul相当于一个数组, li是这个数组里的每一个元素,其实点击 li的时候,只要找打
li在这个数组中的位置即可。
但是它还可以改进,我们完全可以通过事件委托机制来委托每一个 li的点击事件,而不用给每个 li都循环绑定一遍事件。这里还用到了关于类数组对象如何使用原生数组方法的用法。

1
2
3
4
5
document.querySelector('ul').addEventListener('click',function(e){
var target = e.target;
var index=[].indexOf.call(target.parentNode.children,target);
alert(index+1)
})

题后记录:

其实上面这些解答,有些是自己思考得出的,有些是通过搜索引擎搜来的,在搜索的过程中,发现了很多之前模棱两可的概念,特此梳理一下,于是就写了这个文章。这边文章也将作为以后我记录容易混淆概念的地方。

Event.target vs Event.srcElement vs Event.currentTarget vs Event.relatedTarget

Event.target:

指向触发事件的对象。该属性经常在事件代理中用到,通俗点说,你在ul上绑定了一个点击事件,因为事件冒泡机制,点击 ul 中的某个 li会触发 ul 的点击事件,而target就是你点击的这个 li 元素,因为点击了 li 才触发了这个事件, li 触发了该事件。

Event.srcElement

IE6-8不支持 target 属性,只能通过调用 srcElement 来调用

Event.currentTarget

指向绑定该事件的对象,还拿上面那个例子来说,currentTarget 总是指向绑定该事件的元素,所以无论你点击哪个 licurrentTarget 都指向 ul

Event.relatedTarget

这个属性顾名思义——“相关目标”,也就说只有某些事件会关系到一些次要元素时,该属性才会有值,其他的事件该属性都返回 null
那么哪些事件会关系到次要元素呢?
请看下方的表:

这些事件都有一个共同的特性,他们总是成对的,in/out enter/leave out/over enter/exit。

Node vs Element

在事件对象里面我们会看到诸如,childNodes&&childrenparentNode&&parentElement 这些看着很类似的属性,其实只要搞懂了 NodeElement 的区别,就能明白他们之间的区别。

Node:

Node 是一个接口,有很多种类型的DOM元素继承于它,它是节点。
DocumentElement 等等都集成自 Node
可以根据 Node.nodeType 来判断该对象属于哪一种 Node

Element:

Element 是继承自 Node 接口的一个对象,是元素,根据上图可知,当一个 NodenodeType 为1是,它是一个 Element
理解了Node和Element的区别,你就会明白 childNodeschildren 的区别以及 parentNodeparentElement 的区别。
children 集合里的元素全是 Element,而 childNodes 集合里的元素都是 Node,不一定都是 Element
同理,parentNodeparentElement 也是同样的道理。