看一段比较经典的错误代码:

// 希望获取页面上的所有div,在点击的时输出对应的编号
var oDom = document.querySelectorAll("div"); // 事实上,所有的div被点击输出的都是div的个数加1
for (var i = 0; i <= oDom.length-1; i++) {
oDom[i].addEventListener("click", function log() {
console.log(i+1);
}, false);
} // 希望每秒输出对应的编号,事实上输出的全是6
for (var i = 1; i <= 5; i++){
setTimeout(function timer() {
console.log(i)
}, i* 1000);
}

理解上的错误

这两段代码是JavaScript新手很难理解的地方,为什么就不是我希望的结果?这是曾经让我抓狂不已的东西,但我现在懂了。

下面,我来分析一下上面两段代码:

第一段代码中,使用querySelectorAll获取DOM元素,得到的是一个类数组对象NodeList,可以遍历。

此处使用了for循环遍历,目的是,用addEventListener给每一个div都绑定一个click事件,

事件处理函数是输出每一个div所在的编号,比如如果是页面上的第一个div,就会输出1,但输出的却是页面上div的数目加1。

问题其实就在于循环缺陷就是我们假设每一次迭代在运行时都会给自己捕获一个i的副本

所有的事件绑定函数虽然都是在各自的迭代中定义的,但它们都是被封闭到了共享的全局作用域,因为for循环是没有块级作用域的。

在共享的全局作用域下,实际上只有1个i。循环结束后,i的值就是div.length+1,因此输出的是div的数目加1。

第二段代码中,使用了setTimeout延迟函数,目的是每一秒都输出对应的编号,但输出的全是6。

这里的缺陷是:延迟函数的回调总是会在循环结束才执行。即使setTimeout(..., 0),所有的回调依然是在循环结束后才执行的。

循环结束后的运行结果跟第一段代码的运行原理一样。

解决方法

以上两段话,如果理解了,如何让它变成我们希望的结果呢?

答案的本质是让for变成一个封闭的块级作用域

解决方法有两个:

IIFElet,前者是利用IIFE来创建一个块级作用域,也就是所谓的闭包作用域,后者是利用let的特性会自动转换为块级作用域。

代码如下:

// 第一段 IIFE
for (var i = 0; i <= oDom.length-1; i++) {
(function(i) {
oDom[i].addEventListener("click", function log() {
console.log(i+1);
}, false);
})(i)
}
// 第一段 let
for (let i = 0; i <= oDom.length-1; i++) {
oDom[i].addEventListener("click", function log() {
console.log(i+1);
}, false);
}
// 第二段 IIFE
for (var i = 1; i <= 5; i++) {
(function (i) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
})(i);
}
// 第二段 let
for (let i = 1; i <= 5; i++){
setTimeout(function timer() {
console.log(i)
}, i* 1000);
}

如果有新手再问你这个问题,你可以自豪地跟他说:这个问题曾经让我抓狂,但我现在懂了。然后花一点时间,给他好好整整思路。

参考

  • 你不知道的JavaScript

JavaScript中的循环和闭包的更多相关文章

  1. 前端学习 第六弹: javascript中的函数与闭包

    前端学习 第六弹:  javascript中的函数与闭包 当function里嵌套function时,内部的function可以访问外部function里的变量 function foo(x) {   ...

  2. JavaScript中For循环以及For循环嵌套实例

    JavaScript中For循环实例 1.打印出所有的 "水仙花数 ",所谓 "水仙花数 "是指一个三位数,其各位数字立方和等于该数本身. 例如:153是一个 ...

  3. Javascript中的循环变量声明,到底应该放在哪儿?

    相信很多Javascript开发者都在声明循环变量时犹豫过var i到底应该放在哪里:放在不同的位置会对程序的运行产生怎样的影响?哪一种方式符合Javascript的语言规范?哪一种方式和ecma标准 ...

  4. javascript中的原型和闭包

    定义 //闭包测试 function bbTest() { var local = "这里是本地变量"; //闭包会扩大局部变量的作用域,具备变量一致会存活到函数之外,在函数之外可 ...

  5. [译]Javascript中的循环

    本文翻译youtube上的up主kudvenkat的javascript tutorial播放单 源地址在此: https://www.youtube.com/watch?v=PMsVM7rjupU& ...

  6. JavaScript 中 for 循环

    在ECMAScript5(简称 ES5)中,有三种 for 循环,分别是: 简单for循环 for-in forEach 在2015年6月份发布的ECMAScript6(简称 ES6)中,新增了一种循 ...

  7. JavaScript基础Javascript中的循环(003)

    1.普通循环JavaScript中一般的循环写法是这样的: // sub-optimal loop for (var i = 0; i < myarray.length; i++) { // d ...

  8. JavaScript中的作用域和闭包

    首先强烈安利<你不知道的JavaScript>,JS初学者进阶必读. 对于从C++.Java等静态语言转向JavaScript的初学者(比如我)来说,JS一些与众不同而又十分要紧的特性使得 ...

  9. JavaScript中的函数:闭包,this,高阶函数

    一.函数基本理论 function compare(val1,val2){ return val1 - val2; }var result = compare(5,10); 1,函数的定义没什么意义, ...

随机推荐

  1. 如何入侵SF服务器/充当GM刷元宝

    首要作者本人要声明一下,写下此文章技术不是教你去黑传奇SF,只是想以本文引起4F拥有者的留意方案,哈哈. 如何入侵传奇SF刷元宝,首先要温故下自己的专业技术水平. 我也非常喜欢玩游戏,但却玩得特别菜, ...

  2. skyline加载arcgis发布的wms服务

    function AddWMSLayer(LayerName) {var _WMSUrl =“http://10.0.4.141:6080/arcgis/services/poss1/MapServe ...

  3. ORA-07217 environment variable cannot be evaluated

    问题描述:还是rman的问题,一个很沙雕的问题,改了半天,准备是要做数据库的全备,和归档的备份 1.连接rman进行备份,这里要保持数据库为mount状态,因为要对数据库全备 [oracle@orcl ...

  4. 原生js的2048的制作过程

    1.首先我们来看一下效果图 开始: 结束: 接下来我们来实现代码部分: HTML部分: 2048大家应该都玩过,首先我们要准备16个盒子让它4*4排列,这里的css我就不说了,这应该使我们都会的,在这 ...

  5. Sqoop 抽数报错: java.io.FileNotFoundException: File does not exist

    Sqoop 抽数报错: java.io.FileNotFoundException: File does not exist 一.错误详情 2019-10-17 20:04:49,080 INFO [ ...

  6. 拓展 centos 7

    查看端口的占用情况 Centos6/contos7(ECS) 使用 netstat 查询系统上有多少TCP 多少UDP会话 netstat -tun -t: tcp -u: udp -a: all 表 ...

  7. Grafana = 可视化分析 + 监控告警

    Grafana是一个完美地分析和监控的开发平台 可以把Grafana理解为一个可视化面板(Dashboard),其实Kibana也是一个分析和可视化平台,只不过在大家的日常使用中Kibana是跟着Lo ...

  8. Android lifecyle 源码解剖 - gdutxiaoxu的博客(微信公众号 stormjun94)

    版权声明:本文为博主原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/gdutxiaoxu/article/det ...

  9. Android 无源码smail进行debug

    待调试项目在AndroidManifest.xml中debugable=true 参照此处:https://blog.csdn.net/ausboyue/article/details/8018918 ...

  10. 使用gitolite搭建Git服务器

    使用gitolite搭建Git服务器 运行环境 Ubuntu18.04 gitolite 搭建过程 安装好Ubuntu18.04系统 更新系统 sudo apt update sudo apt upg ...