Nodejs 数组的队列以及forEach的应用详解


Posted in NodeJs onFebruary 25, 2021

本文主要记录了在Nodejs开发过程中遇到过的由数组特性引起的问题及解决方式,以及对数组的灵活应用。

本文代码测试结果均基于node v6.9.5

数组与队列

利用数组对象方法push/shift可实现队列先进先出特性,例如:

>a=[]
[]
>a.push(2.3.4)
3
>a.push(2)
3
>a
[2.3.4.2]
>a.shift()
2
>a
>[3.4.2]

数组与forEach

对数组的删除操作有两种常见方式:delete和使用splice方法,需要明确他们的区别。

操作/方法 说明
splice 删除并返回指定的数组元素,数组本身长度会改变;但不会free元素对象
delete 删除(free)元素对象,数组元素不变,值变为undefined

如果要从数组中彻底删除某个元素,使用splice即可:

> a=[1,2,3]
[ 1, 2, 3 ]
> a.splice(1,1)
[ 2 ]
> a
[ 1, 3 ]
> a.length
2
> a.forEach(function(item, index){console.info("index[", index,"]:", item)});
index[ 0 ]: 1
index[ 1 ]: 3
undefined
>

那么,当使用delete删除某个元素对象后,此时执行forEach的效果是什么?

forEach对含空元素数组处理机制

测试结果如下

> a=[1,2,3]
[ 1, 2, 3 ]
> delete a[1]
true
> a
[ 1, , 3 ]
> a.length
3
> a.forEach(function(item, index){console.info("index[", index,"]:", item)});
index[ 0 ]: 1
index[ 2 ]: 3
undefined

从测试结果来看,forEach并不会遍历到值为undefined的哪一项。这在实际应用中如何判断forEach是否结束是一大挑战。

解决配合forEach的异步特性应用,可为数组添加prototype来自行管理设置有效数据;

效果如下:

> a=[1,2,3]
[ 1, 2, 3 ]
> a.validnum=3
3
> delete a[2]
true
> a.validnum=2
2
> a
[ 1, 2, , validnum: 2 ]
> a.length
3
> a.validnum
2
> a.forEach(function(item, index){console.info("index[", index,"]:", item)});
index[ 0 ]: 1
index[ 1 ]: 2
undefined
>

补充:Node.js 数组 forEach 同步处理上下文语句

习惯了C语言系的思维方式,刚接触Node.js,它的异步处理让我头大。

写代码遇到这么一个场景,需要循环对一个数组中的元素进行处理,全部处理完成后再执行一个last操作。但是JS的异步特性会使这个last语句先执行,所以花点时间研究研究forEach。

Talk is cheap. Show me the code.

forEach 用法

forEach用于对数组结构进行遍历,看到有人说forEach底层是用for实现的,没深究,起码效果上看是一样的。forEach的回调函数3个参数分别是:值、序号和原数组。序号从0开始。

(() => {
  let arr = [2, 3, 1];
  arr.forEach(function (value, index, array) {
    console.log(value);
    console.log(index);
    console.log(array);
    console.log('-----');
  });
})();

Output

2
0
[ 2, 3, 1 ]
-----
3
1
[ 2, 3, 1 ]
-----
1
2
[ 2, 3, 1 ]
-----

从结果上看forEach多次循环之间是同步的,也就是说都是按顺序执行的。但是一想到它是JS就感觉不可能同步的。。可以验证一下。

forEach 异步处理多次循环

这次在forEach加个定时任务,每次循环操作都延时value相关的时间,模拟比较耗时的操作。

(() => {
  let arr = [2, 3, 1];
  arr.forEach(function (value, index, array) {
    setTimeout(function () {
      console.log(value);
    }, value*100);
  });
})();

Output

1
2
3

从结果可以看出耗时最短的任务先完成,每次循环的任务并不是按循环的先后顺序执行的,也就是说异步处理多次循环。

forEach 上下文也是异步执行

回到开始说到的问题了,且不管多次循环是不是按顺序执行,我需要forEach中的所有任务都完成后执行一条数据来通知我任务全部完成了。

(() => {
  let arr = [2, 3, 1];
  arr.forEach(function (value, index, array) {
    setTimeout(function () {
      console.log(value);
    }, value*100);
  });
  console.log('All the work is done');
})();

Output

All the work is done
1
2
3

从结果来看,上下文的语句也不是同步的,forEach循环中的任务没有完成就通知所有任务都完成了,显然不符合预期。

针对这个问题看了好多个博客,都没有找到合适的解决方法,最后只能想到用Promise.all来勉强实现这个功能。

Promise.all 实现 forEach 上下文语句同步处理

把上面的代码改成Promise.all的结构。每个循环中执行结束调用resolve(),我们知道Promise.all的then函数,只有所有的Promise都执行完成才会触发,这样好像能满足我们的需求。

(() => {
  let arr = [2, 3, 1];
  let proArr = [];
  arr.forEach(function (value, index) {
    proArr[index] = new Promise(function (resolve) {
      setTimeout(function () {
        console.log(value);
        resolve();
      }, value*100);
    });
  });
  Promise.all(proArr).then(()=>{
    console.log('All the work is done');
  })
})();

Output

1
2
3
All the work is done

从结果来看,满足了我们的需求。

可能还存在的问题

想到JS异步特性,突然发现可能这个方法还存在个问题。

这里每次 forEach 刚进入就对 Promise 数组进行了赋值操作,这个操作时间应该非常短,循环3次都赋值完成后才调用最后的Promise.all语句。

但是如果这个数组非常大,这个循环赋值的操作非常耗时间的话,假如只完成了一半的赋值操作,那么执行最后这个 Promise.all 的时候传入的 Promise 数组可能并不是包含所有 Promise 的数组。

这样的话 Promise.all 等待的就只有一半的操作,Promise.all 等待的时候,这个数组后面被赋值的 Promise 不知道会不会被等待。

刚接触JS不明白实现机制,只能实验来验证一下是否存在这个问题。接下来用把这个数组弄大一些,请原谅我用最傻瓜式的方式搞大它。

(() => {
  let arr = [2, 3, 1, 2, 3, 1, 2, 3, 1, 2];  // 10
  arr= arr.concat(arr);  // 2^1 * 10
  arr= arr.concat(arr);  // 2^2 * 10
  arr= arr.concat(arr);  // 2^3
  arr= arr.concat(arr);  // 2^4
  arr= arr.concat(arr);  // 2^5
  arr= arr.concat(arr);
  arr= arr.concat(arr);
  arr= arr.concat(arr);
  arr= arr.concat(arr);
  arr= arr.concat(arr);  // 2^10
  arr= arr.concat(arr);
  arr= arr.concat(arr);
  arr= arr.concat(arr);
  arr= arr.concat(arr);
  arr= arr.concat(arr);  // 2^15
  arr= arr.concat(arr);
  arr= arr.concat(arr); // 2^17 * 10
// arr= arr.concat(arr);  // 2^18 * 10
  console.log(arr.length);
  let proArr = [];
  arr.forEach(function (value, index) {
    proArr[index] = new Promise(function (resolve) {
      setTimeout(function () {
        console.log(value);
        resolve();
      }, value*100);
    });
  });
  Promise.all(proArr).then(()=>{
    console.log('All the work is done');
    console.log(arr.length);
  }).catch(function (err) {
    console.log(err);
  })
})();

经过测试在我这个电脑上当数组长度为2^18 * 10的时候,Promise报错 RangeError: Too many elements passed to Promise.all。

当数组长度为2^17 * 10 即2621440的时候,会正常运行。测试了几次,最后的执行命令输出的All the work is done始终在最后输出(因为终端缓冲区太小,所以使用node xx.js > log.txt重定向的方式把输出结果重定向到文件查看)。

当然应用中也不会有这么大的数组,从结果看的话,就是实际应用中不存在上面考虑可能出现的问题。

也就是说可以用 Promise.all 实现 forEach 上下文语句同步处理。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持三水点靠木。如有错误或未考虑完全的地方,望不吝赐教。

NodeJs 相关文章推荐
Nodejs学习笔记之Stream模块
Jan 13 NodeJs
NodeJS Web应用监听sock文件实例
Feb 18 NodeJs
详解nodejs 文本操作模块-fs模块(二)
Dec 22 NodeJs
NodeJS配置HTTPS服务实例分享
Feb 19 NodeJs
nodejs个人博客开发第二步 入口文件
Apr 12 NodeJs
深入理解nodejs中Express的中间件
May 19 NodeJs
深入浅析Nodejs的Http模块
Jun 20 NodeJs
nodejs中解决异步嵌套循环和循环嵌套异步的问题
Jul 12 NodeJs
nodejs初始化init的示例代码
Oct 10 NodeJs
M2实现Nodejs项目自动部署的方法步骤
May 05 NodeJs
NodeJS有难度的面试题(能答对几个)
Oct 09 NodeJs
nodejs+express最简易的连接数据库的方法
Dec 23 NodeJs
一文秒懂nodejs中的异步编程
Jan 28 #NodeJs
在nodejs中创建child process的方法
Jan 26 #NodeJs
nodejs中使用worker_threads来创建新的线程的方法
Jan 22 #NodeJs
Nodejs 微信小程序消息推送的实现
Jan 20 #NodeJs
Nodejs实现微信分账的示例代码
Jan 19 #NodeJs
nodejs中的异步编程知识点详解
Jan 17 #NodeJs
nodejs+express最简易的连接数据库的方法
Dec 23 #NodeJs
You might like
php正则表达式(regar expression)
2011/09/10 PHP
PHP改进计算字符串相似度的函数similar_text()、levenshtein()
2014/10/27 PHP
ThinkPHP中类的构造函数_construct()与_initialize()的区别详解
2017/03/13 PHP
PHP实现基于栈的后缀表达式求值功能
2017/11/10 PHP
使用PHP开发留言板功能
2019/11/19 PHP
让 JavaScript 轻松支持函数重载 (Part 2 - 实现)
2009/08/04 Javascript
用 Javascript 验证表单(form)中多选框(checkbox)值
2009/09/08 Javascript
跨浏览器的 mouseenter mouseleave 以及 compareDocumentPosition的使用说明
2010/05/04 Javascript
js 判断checkbox是否选中的实现代码
2010/11/23 Javascript
基于jquery的DIV随滚动条滚动而滚动的代码
2012/07/20 Javascript
原生javascript和jquery判断浏览器版本等信息
2013/07/04 Javascript
原生javascript实现无间缝滚动示例
2014/01/28 Javascript
jquery实现的图片点击滚动效果
2014/04/29 Javascript
NodeJS学习笔记之网络编程
2014/08/03 NodeJs
JSON与XML优缺点对比分析
2015/07/17 Javascript
Javascript基础_简单比较undefined和null 值
2016/06/14 Javascript
原生JS封装ajax 传json,str,excel文件上传提交表单(推荐)
2016/06/21 Javascript
vuejs2.0实现一个简单的分页示例
2017/02/22 Javascript
关于在vue 中使用百度ueEditor编辑器的方法实例代码
2018/09/14 Javascript
JavaScript继承的特性与实践应用深入详解
2018/12/30 Javascript
vue缓存之keep-alive的理解和应用详解
2020/11/02 Javascript
python绘制条形图方法代码详解
2017/12/19 Python
python collections模块的使用
2020/10/16 Python
最新Python idle下载、安装与使用教程图文详解
2020/11/28 Python
StubHub墨西哥:购买和出售您的门票
2016/09/17 全球购物
Oral-B荷兰:牙医最推荐的品牌
2020/02/25 全球购物
腾讯技术类校园招聘笔试试题
2014/05/06 面试题
精选干货:Java精选笔试题附答案
2014/01/18 面试题
大学生简短的自我评价分享
2014/02/20 职场文书
厨师个人自我鉴定范文
2014/04/19 职场文书
应聘英语教师求职信
2014/04/24 职场文书
2014乡镇领导班子四风对照检查材料思想汇报
2014/10/05 职场文书
乡镇党的群众路线教育实践活动制度建设计划
2014/11/03 职场文书
Python+Appium实现自动抢微信红包
2021/05/21 Python
MySQL 1130异常,无法远程登录解决方案详解
2021/08/23 MySQL
SSM项目使用拦截器实现登录验证功能
2022/01/22 Java/Android