JavaScript中的循环和闭包

看一段比较经典的错误代码:
// 希望获取页面上的所有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变成一个封闭的块级作用域。
解决方法有两个:
IIFE和let,前者是利用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中的循环和闭包的更多相关文章
- 前端学习 第六弹: javascript中的函数与闭包
前端学习 第六弹: javascript中的函数与闭包 当function里嵌套function时,内部的function可以访问外部function里的变量 function foo(x) { ...
- JavaScript中For循环以及For循环嵌套实例
JavaScript中For循环实例 1.打印出所有的 "水仙花数 ",所谓 "水仙花数 "是指一个三位数,其各位数字立方和等于该数本身. 例如:153是一个 ...
- Javascript中的循环变量声明,到底应该放在哪儿?
相信很多Javascript开发者都在声明循环变量时犹豫过var i到底应该放在哪里:放在不同的位置会对程序的运行产生怎样的影响?哪一种方式符合Javascript的语言规范?哪一种方式和ecma标准 ...
- javascript中的原型和闭包
定义 //闭包测试 function bbTest() { var local = "这里是本地变量"; //闭包会扩大局部变量的作用域,具备变量一致会存活到函数之外,在函数之外可 ...
- [译]Javascript中的循环
本文翻译youtube上的up主kudvenkat的javascript tutorial播放单 源地址在此: https://www.youtube.com/watch?v=PMsVM7rjupU& ...
- JavaScript 中 for 循环
在ECMAScript5(简称 ES5)中,有三种 for 循环,分别是: 简单for循环 for-in forEach 在2015年6月份发布的ECMAScript6(简称 ES6)中,新增了一种循 ...
- JavaScript基础Javascript中的循环(003)
1.普通循环JavaScript中一般的循环写法是这样的: // sub-optimal loop for (var i = 0; i < myarray.length; i++) { // d ...
- JavaScript中的作用域和闭包
首先强烈安利<你不知道的JavaScript>,JS初学者进阶必读. 对于从C++.Java等静态语言转向JavaScript的初学者(比如我)来说,JS一些与众不同而又十分要紧的特性使得 ...
- JavaScript中的函数:闭包,this,高阶函数
一.函数基本理论 function compare(val1,val2){ return val1 - val2; }var result = compare(5,10); 1,函数的定义没什么意义, ...
随机推荐
- python多线程编程-queue模块和生产者-消费者问题
摘录python核心编程 本例中演示生产者-消费者模型:商品或服务的生产者生产商品,然后将其放到类似队列的数据结构中.生产商品中的时间是不确定的,同样消费者消费商品的时间也是不确定的. 使用queue ...
- vscode 同步扩展插件
第一步: 在 VSCode 中,安装用于同步配置的插件 settings sync 第二步:将 VSCode 配置上传到 GitHub 完成这一步需要 GitHub token 和 GitHu ...
- 使用GDAL/GEOS求面特征的并集
存在这样一个示例的矢量文件,包含了两个重叠的面特征: 一个很常见的需求是求取这个矢量中所有面元素的并集,通过GDAL/GEOS很容易实现这个功能,具体代码如下: #include <iostre ...
- 软件开发工具(第13章: Eclipse插件的使用与开发)
一.插件简介 插件的定义(了解) 插件是一种遵循其所依附的软件的接口规范所编写出来的程序. 插件实际上是对原有软件的扩展,替应用程序增加一些所需要的特定 功能. 插件的构成(重点.记忆) 每个插件都由 ...
- python-基础-isinstance(p_object, class_or_type_or_tuple)
1.isinstance(p_object, class_or_type_or_tuple) p_object:实例 class_or_type_or_tuple:类型,可以是一个类型或者是组成的元组 ...
- React-Native三种断点调试方式的流程和优缺点比较
RN的调试和web端的调试虽然相似,但是也有一些不同,下面就来比较一下三种断点调试方法的差异 总结: 感觉还是第一种好一些 1.React-Native-Debugger工具调试法 1.1 首先我们得 ...
- eruda手机端调试神器
在日常的移动端开发时,一般都是试用chrome浏览器的移动端模式进行开发和调试,如果想在手机上能和浏览器一样看控制台调试就更加完美了: 一个手机端调试神器eruda是一个专为手机网页前端设计的调试面板 ...
- 计算几何 val.3
目录 计算几何 val.3 自适应辛普森法 定积分 引入 辛普森公式 处理精度 代码实现 模板 时间复杂度 练习 闵可夫斯基和 Pick定理 结论 例题 后记 计算几何 val.3 自适应辛普森法 可 ...
- Prometheus学习系列(八)之Prometheus API说明
前言 本文来自Prometheus官网手册 和 Prometheus简介 HTTP API 在Prometheus服务器上的/api/v1下可以访问当前稳定的HTTP API. 将在该端点下添加任何非 ...
- 精通awk系列(10):awk筛选行和处理字段的示例
回到: Linux系列文章 Shell系列文章 Awk系列文章 awk数据筛选示例 筛选行 # 1.根据行号筛选 awk 'NR==2' a.txt # 筛选出第二行 awk 'NR>=2' a ...