需要同时为多个元素绑定事件并绑定数据,但如果没有理解透彻作用域的有效范围与事件响应机制,常常会发生数据绑定错误甚至代码执行错误。
应用场景:从后台返回了多个指标类型(数量不定),需要将这些指标类型绑定到多个按钮上,每次点击按钮时,将该指标的id提交到后台,从而能获取到该类型的指标数据,假定后台返回的数据如下所示。
// 后台返回的数据
var datas = [{
id : 1,
name : "CPU"
}, {
id : 2,
name : "内存"
}, {
id : 3,
name : "硬盘"
}]
常用的操作方法都是遍历数据,然后依次创建按钮,并为按钮注册事件,如下:
var i = 0,
btnInd = null;
for(var i = 0, len = datas.length; i < len; i ++) {
btnInd = document.createElement('button');
btnInd.textContent = datas[i].name;
// 将按钮插入页面中,这里是body,业务看需求
document.body.appendChild(btnInd);
btnInd.addEventListener('click', function() {
// 以alert来代替发送ajax请求
alert(datas[i].id);
})
}
执行上述代码,结果令人惊讶,竟然是“Uncaught TypeError: Cannot read property ‘id’ of undefined”,为什么呢?原因是在用户触发点击事件时,i已经变为len了(这里是3)。
那为何回调函数没有与主函数的作用域进行绑定呢?因为所有的按钮都共用一个变量i,而i值在方法的主进程里一直在发生变化,使用多个变量后代码如下。
var i = 0,
i0 = 0, // 声明多个变量
i1 = 1,
btnInd = null;
for(var i = 0, len = datas.length; i < len; i ++) {
btnInd = document.createElement('button');
btnInd.textContent = datas[i].name;
// 将按钮插入页面中,这里是body,业务看需求
document.body.appendChild(btnInd);
// 也可以用if
switch(i) {
case 0:
btnInd.addEventListener('click', function() {
// 以alert来代替发送ajax请求
// 也可以直接使用零
alert(datas[i0].id);
})
break;
case 1:
btnInd.addEventListener('click', function() {
// 以alert来代替发送ajax请求
alert(datas[i1].id);
})
break;
// 这样的代码实在是太low了,剩余的代码,不一一列举
……
}
}
上面的办法虽然解决了问题,但实在是太low了,并且需要知道迭代的次数,在数组容量稍微大一点的情况下,那就是程序员的噩梦。
解决的办法有三种,分别列举如下:
一、 面向对象解决法, 充分利用this;
//在这里为按钮添加属性
btnInd.ajaxId = datas[i].id
btnInd.addEventListener('click', function() {
// 以alert来代替发送ajax请求
// 通过this来指向所需要的数据
alert(this.ajaxId);
})
二、 函数解决法;
// 在循环外添加函数registerEvent
function registerEvent(btn, id) {
btn.addEventListener('click', function() {
// 以alert来代替发送ajax请求
// 通过this来指向所需要的数据
alert(id);
})
}
// 循环内部的注册代码改为
document.body.appendChild(btnInd);
registerEvent(btnInd, datas[i].id)
三、 es6段作用域解决法;
// 修改for循环的迭代值声明方式,把var改为let
// 目前浏览器还不支持ES 6,需要编译后才能执行
for(let i = 0, len = datas.length; i < len; i ++) {
// 其中的代码与第一段注册的代码完全相同
}