nodejs 循环的陷阱
Node.js 的异步机制由事件和回调函数实现,一开始接触可能会感觉违反常规,但习惯
以后就会发现还是很简单的。然而这之中其实暗藏了不少陷阱,一个很容易遇到的问题就是
循环中的回调函数,初学者经常容易陷入这个圈套。让我们从一个例子开始说明这个问题。
//forloop.js
var fs = require('fs');
var files = ['a.txt', 'b.txt', 'c.txt'];
for (var i = 0 ; i < files.length; i++) {
fs.readFile(files[i], 'utf-8', function (err, contents) {
console.log(files[i] + ': ' + contents);
});
}
这段代码的功能很直观,就是依次读取文件 a.txt、b.txt 、c.txt ,并输出文件名和内容。
假设这三个文件的内容分别是 AAA 、BBB 和 CCC,那么我们期望的输出结果就是:
a.txt: AAA
b.txt: BBB
c.txt: CCC
可是我们运行这段代码的结果是怎样的呢?竟然是这样的结果:
undefined: AAA
undefined: BBB
undefined: CCC
这个结果说明文件内容正确输出了,而文件名却不对,也就意味着,contents 的结果
是正确的,但 files[i] 的值是 undefined。这怎么可能呢,文件名不正确却能读取文件
内容?既然难以直观地理解,我们就把 files[i] 分解并打印出来看看,在读取文件的回调
函数中分别输出 files、i 和 files[i] 。
//forloopi.js
var fs = require('fs');
var files = ['a.txt', 'b.txt', 'c.txt'];
for (var i = 0 ; i < files.length; i++) {
fs.readFile(files[i], 'utf-8', function (err, contents) {
console.log(files);
console.log(i);
console.log(files[i]);
});
}
运行修改后的代码,结果如下:
[ 'a.txt', 'b.txt', 'c.txt' ]
3
undefined
[ 'a.txt', 'b.txt', 'c.txt' ]
3
undefined
[ 'a.txt', 'b.txt', 'c.txt' ]
3
undefined
看到这里是不是有点启发了呢?三次输出的 i 的值都是 3 ,超出了 files 数组的下标
范围,因此 files[i] 的值就是 undefined 了。这种情况通常会在 for 循环结束时发
生,例如 for (var i = 0; i < files.length; i++),退出循环时 i 的值就是
files.length 的值。既然 i 的值是 3 ,那么说明了事实上 fs.readFile 的回调函数中
访问到的 i 值都是循环退出以后的,因此不能分辨。而 files[i] 作为 fs.readFile 的
第一个参数在循环中就传递了,所以文件可以被定位到,而且可以显示出文件的内容。
现在问题就明朗了:原因是3 次读取文件的回调函数事实上是同一个实例,其中引用到
的 i 值是上面循环执行结束后的值,因此不能分辨。如何解决这个问题呢?我们可以利用
JavaScript 函数式编程的特性,手动建立一个闭包:
//forloopclosure.js
var fs = require('fs');
var files = ['a.txt', 'b.txt', 'c.txt'];
for (var i = 0 ; i < files.length; i++) {
( function (i) {
fs.readFile(files[i], 'utf-8', function (err, contents) {
console.log(files[i] + ': ' + contents);
});
})(i);
}
6.2 控制流 137
1
2
3
5
7
10
8
9
4
6
上面代码在 for 循环体中建立了一个匿名函数,将循环迭代变量 i 作为函数的参数传
递并调用。由于运行时闭包的存在,该匿名函数中定义的变量(包括参数表)在它内部的函
数( fs.readFile 的回调函数)执行完毕之前都不会释放,因此我们在其中访问到的 i 就
分别是不同的闭包实例,这个实例是在循环体执行的过程中创建的,保留了不同的值。
事实上以上这种写法并不常见,因为它降低了程序的可读性,故不推荐使用。大多数情
况下我们可以用数组的 forEach 方法解决这个问题:
//callbackforeach.js
var fs = require('fs');
var files = ['a.txt', 'b.txt', 'c.txt'];
files.forEach(function (filename) {
fs.readFile(filename, 'utf-8', function (err, contents) {
console.log(filename + ': ' + contents);
});
});
nodejs 循环的陷阱的更多相关文章
- shell中while循环的陷阱
在写while循环的时候,发现了一个问题,在while循环内部对变量赋值.定义变量.数组定义等等环境,在循环外面失效. 一个简单的测试脚本如下: #!/bin/bash echo "abc ...
- nodejs 循环中操作需要同步执行解决方案
最近用nodejs做了个针对某网站的小爬虫.干坏事得低调对吧,不能同时开太多的网络访问,结果各种回调/循环虐的心力交瘁. 经过了n次的百度\哥哥后终于拼出了自己要的功能.不敢独享分享出来以供大家参考. ...
- js for循环的陷阱
☞问题概述 一页面有三个按钮,点击提示相应内容.相应内容已从后台获取,并转化成json数组. var content = ["提示1", "提示2", &quo ...
- js中FOR循环的陷阱
//闭包解决 循环输出的问题 for(var i=0;i<rows.length;i++) {( function (i) { })(i);
- Java日志——2016.6.3
1)二维数组的静态初始化: int[][] arr = new int[][] { {1,2,3}, {4,5}, {6,7}} 2)杨辉三角: /** * 需求: ...
- 完美C++(第5版)(双色)
完美C++(第5版)(双色) 薛正华 沈庚 韦远科 译 ISBN 978-7-121-23198-8 2014年6月出版 定价:148.00元 788页 16开 内容提要 <完美C++(第5版) ...
- Node.js模块 加载笔记
//核心模块就是Node.js标准API种提供的模块,如fs,http,net.vm等.官方提供,编译成二进制代码//核心模块拥有最高的加载优先级 //文件模块则是存储为单独的文件(或文件夹)的模块, ...
- PageRank之基于C C#的基本实现
重点不是说PageRank是什么,而是怎么用代码实现 什么是PageRank? PageRank,网页排名,又称网页级别.Google左侧排名或佩奇排名,是一种由[1] 根据网页之间相互的超链接计算 ...
- Linux和Shell回炉复习系列文章总目录
本页内容都是本人回炉Linux时整理出来的.这些文章中,绝大多数命令类内容都是翻译.整理man或info文档总结出来的,所以相对都比较完整. 本人的写作方式.风格也可能会让朋友一看就恶心到直接右上角叉 ...
随机推荐
- Android相关工具下载(ADT、NDK等等)
一个非常牛掰的网站,可以下载很多Android相关的工具等 网址为: http://www.androiddevtools.cn/
- 爬虫基本操作、requests和BeautifulSoup
1. 爬虫基本操作 例如舆情系统: 获取汽车之家新闻放到自己数据库里,创建自己的app,发布内容,注明来源,自己创业. URL指定内容获取到 - 发送Http请求:http://www.autohom ...
- 为什么应使用 Node.js
为什么应使用 Node.js JavaScript 高涨的人气带来了很多变化,以至于如今使用其进行网络开发的形式也变得截然不同了.就如同在浏览器中一样,现在我们也可以在服务器上运行 JavaScrip ...
- jmeter后置处理器之正則表達式提取器
新浪围脖>@o蜗牛快跑o 使用这个组件时,注意使用带分组的正則表達式 使用正则分组方便提取干净数据.以免再次处理数据字符串 正則表達式在线工具推荐:点击打开链接 正則表達式语法參考:点击打 ...
- Integrate NSX into Neutron
NSX is VMware's strategy for Software-defined networking, it was implemented purely in software, and ...
- Sass编译css/Grunt压缩文件
Sass安装(mac) $ sudo gem install sass scss编译成css文件 $ sass ui.scss ui.css CLI安装 $ npm install -g grunt- ...
- JAVA 数据筛选(第一笔数据与第二笔数据比较)
第一笔数据与第二笔数据比较 Map<String, Object> jHpictureMap = new HashMap<String, Object>(); // 存放照片S ...
- Server.xml配置详解
<Server port="8005" shutdown="SHUTDOWN"> <!-- 属性说明 port:指定一个端口,这个端口负责监听 ...
- Webview页面的控件元素定位
前言 现在有很多App都是Hybrid的,即有原生的页面又有Webview的页面,元素的可以通过uiautomatorviewer工具 进行控件元素的定位,Webview页面的则无法通过此方式定位,而 ...
- C++代码书写模板 -- 如何判断函数类型
先说一个简单的方案. 经过验证 g++ 和 vs2010 都可以.原理就是利用函数类型可以直接转换成函数指针. template<class T> bool test( T * t ) { ...