类似jquery 的 .index();
就一级,类似
<ul id="myul">
<li>click</li>
<li>click</li>
<li>click</li>
<li>click</li>
<li>click</li>
</ul>
var nodeList = document.getElementsByTagName('li');
for(var i = 0;i<nodeList.length;i++){
//nodeList[i].index = i
nodeList[i].addEventListener("click", function(e){
console.log(nodeList.indexOf(nodeList[i]))
}, false);
}
我写的貌似有了闭包的问题,更正我的代码也好,我乱掉了。。。。
请问要如何实现呢?
PHPz2017-04-10 13:14:08
比起“闭包”问题,我更倾向于它是一个作用域的问题。
不少前端都错用了“闭包”这个词,本文将尽量避免使用这个词。
看看你的代码:
var nodeList = document.getElementsByTagName('li');
for (var i = 0; i < nodeList.length; i++) {
nodeList[i].addEventListener("click", function(e) {
console.log(nodeList.indexOf(nodeList[i]))
}, false);
}
ECMAScript规定,独立作用域只能通过“函数(function)”代码类型的执行上下文创建。
其他方式,for循环,{}
代码块,定义函数,全部都不会产生新的作用域。
上述代码中有没有新的作用域?有的,你用了一些形如function(e){}
的匿名函数字面量,在执行时,每一个都会创建一个新的作用域。
你的这些匿名函数字面量不会立即执行,它被设定为,在点击时才执行,这里它仅仅是被定义,被当做参数传入addEventListener函数而已。
在你的代码中,i和nodeList处于你的代码的顶级作用域之中,这个i在内存中只有一份。很显然,你的问题出在“为何让五个回调函数公用一个变量i
”。
——当你点击的时候,for循环早就已经执行完毕,调用会沿着作用域链,逐级往上找,直到找到那个已经用完到达5的i。
所以我们要解决这个问题,必须缓存i。
以下方案在于,在于使用立即执行函数,通过函数的实参缓存i:
var nodeList = document.getElementsByTagName('li');
for (var i = 0; i < nodeList.length; i++) {
nodeList[i].onclick = (function(j){
return function(e) {
alert(j)
};
})(i);
}
var nodeList = document.getElementsByTagName('li');
for (var i = 0; i < nodeList.length; i++) {
(function(j){
nodeList[j].addEventListener("click", function(e) {
alert(j)
}, false);
})(i) ;
}
以下方案其实是使用函数调用缓存实参的变种,它将index缓存在forEach循环的函数调用中:
// 兼容性提示:注意ES5的forEach方法的兼容性
var nodeList = document.getElementsByTagName("li") ,
arr = Array(nodeList.length+1).join("*").split("") ;
arr.forEach(function(val,i){
nodeList[i].addEventListener("click", function() {
alert(i) ;
}, false);
})
之前有个答案用到了数组,其实不需要再额外用立即执行函数。
下面是forEach的实现方式(并不完全,没有包含对边界状态的控制)。
我们可以看到,forEach循环在遍历过程中调用了fn.call()
,通过函数调用,构建了新的函数上下文,缓存了函数当时的实参k。
Array.prototype.forEach = function(fn, context) {
for (var k = 0, length = this.length; k < length; k++) {
fn.call(context, this[k], k, this);
}
};
以下方案在于,把index值缓存在DOM属性中:
var nodeList = document.getElementsByTagName("li");
for (var i = 0; i < nodeList.length; i++) {
nodeList[i].setAttribute("data-i",i) ;
nodeList[i].addEventListener("click", function(e) {
alert(this.getAttribute("data-i"))
}, false);
}
// 兼容性提示:注意HTML5 data api的兼容性
var nodeList = document.getElementsByTagName("li");
for (var i = 0; i < nodeList.length; i++) {
nodeList[i].dataset["i"] = i ;
nodeList[i].addEventListener("click", function(e) {
alert(this.dataset["i"])
}, false);
}
以下方案在于,把index值缓存在HTMLLIElement对象中(也包括Fakefish的答案),将变量查找转为属性查找:
var nodeList = document.getElementsByTagName('li');
for (var i = 0; i < nodeList.length; i++) {
nodeList[i].index = i ;
nodeList[i].addEventListener("click", function() {
alert(this.index)
}, false);
}
以下方案在于利用ES5中的indexOf方法,只是需要预先将DOM元素转为Array:
// 兼容性提示:注意ES5的indexOf方法的兼容性
// 兼容性提示:DOM对象和JS在低版本IE是无法互相转化的
var nodeList = document.getElementsByTagName('li') ,
arrNodes = Array.prototype.slice.call(nodeList) ,
onclickfunc = function(evt){
alert( arrNodes.indexOf(evt.target) )
} ;
for( var i = 0 ; i < arrNodes.length ; i ++ ) {
arrNodes[i].onclick = onclickfunc ;
}
// 兼容性提示:注意ES5的indexOf方法的兼容性
// 兼容性提示:DOM对象和JS在低版本IE是无法互相转化的
var nodeList = document.getElementsByTagName('li') ,
arrNodes = Array.prototype.slice.call(nodeList) ,
nodeUls = document.getElementsByTagName('ul') ;
nodeUls[0].addEventListener("click",function(evt){
alert(arrNodes.indexOf(evt.target))
},false);
LZ把几种方案混用了哦(包括在博客中),其实是没有必要的。
本想扔几个链接详细解释词法作用域的,结果发现大部分原文都消失了。前端就是这样,好文章混杂在一大堆烂解释中,还动不动就消失……所以说做好知识储备是非常重要的。
还好汤姆大叔的博客还在^^,这个系列真是百推不厌。好好读一读其中的第十一篇到第十六篇吧:)
阿神2017-04-10 13:14:08
javascript不熟悉,代码未测试,但是我觉得应该是这个思路。
取得元素的父元素,再获取父元素所有的子元素,循环遍历来确认具体位置。
el.parentNode.children.indexOf(el)
如果是获取当前节点的所有相邻节点,可以这样搞:
Array.prototype.filter.call(el.parentNode.children, function(child){
return child !== el;
});
这里有一个蛮有意思的东西你可以看看
You might not need JQuery
天蓬老师2017-04-10 13:14:08
[].slice.call(document.getElementById("myul").children).forEach(function(el, index){
(function(index){
el.addEventListener("click", function(){
alert(index);
});
}(index));
});
我猜 你的面试官 是想看到这样的
大家讲道理2017-04-10 13:14:08
var ulNode=document.getElementById("myul");
var liNodes=ulNode.childNodes||ulNode.children;
for(var i=0;i<liNodes.length;i++){//不同浏览器中childNodes的返回值不同,兼容浏览器
if(liNodes[i].nodeType!=1){
ulNodes.removeChild(liNodes[i]);
}
};
for(var i=0;i<liNodes.length;i++){
liNodes[i].index=i;
};
ulNode.addEventListener('click',function(e){
if(e.target.nodeName.toUpperCase()=="LI")
alert(e.target.index);
},false);
大家讲道理2017-04-10 13:14:08
突然看到这个问题 虽然已经有好多人答了还是发了一波
var liList = document.getElementsByTagName('li');
var list = Array.prototype.slice.call(liList,0);
list.forEach(function(item,index){
item.onclick = function(){
console.log(index);
}
});