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实现的一个简单udp广播服务器、客户端
Sep 25 NodeJs
Nodejs高扩展性的模板引擎 functmpl简介
Feb 13 NodeJs
利用nodejs监控文件变化并使用sftp上传到服务器
Feb 18 NodeJs
nodejs服务搭建教程 nodejs访问本地站点文件
Apr 07 NodeJs
Nodejs搭建wss服务器教程
May 24 NodeJs
nodejs 图片预览和上传的示例代码
Sep 30 NodeJs
webstorm中配置nodejs环境及npm的实例
May 15 NodeJs
Nodejs实现爬虫抓取数据实例解析
Jul 05 NodeJs
NodeJS 将文件夹按照存放路径变成一个对应的JSON的方法
Oct 17 NodeJs
手把手教你如何使用nodejs编写cli命令行
Nov 05 NodeJs
用Electron写个带界面的nodejs爬虫的实现方法
Jan 29 NodeJs
nodejs log4js 使用详解
May 31 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 下载文件时自动添加bom头的方法实例
2014/01/10 PHP
php反射应用示例
2014/02/25 PHP
php中mkdir()函数的权限问题分析
2016/09/24 PHP
验证坐标在某坐标区域内php代码
2016/10/08 PHP
swoole和websocket简单聊天室开发
2017/11/18 PHP
javascript 获取图片颜色
2009/04/05 Javascript
JSON 和 JavaScript eval使用说明
2010/06/13 Javascript
JavaScript 变量作用域分析
2011/07/04 Javascript
用jQuery中的ajax分页实现代码
2011/09/20 Javascript
jquery无刷新验证邮箱地址实现实例
2014/02/19 Javascript
jquery实现背景墙聚光灯效果示例分享
2014/03/02 Javascript
javascript实例分享---具有立体效果的图片特效
2014/06/08 Javascript
jQuery1.9+中删除了live以后的替代方法
2016/06/17 Javascript
jQuery遍历DOM的父级元素、子级元素和同级元素的方法总结
2016/07/07 Javascript
js实现常用排序算法
2016/08/09 Javascript
jQueryUI 拖放排序遇到滚动条时有可能无法执行排序的小bug及解决方案
2016/12/19 Javascript
Javascript设计模式之装饰者模式详解篇
2017/01/17 Javascript
总结JavaScript在IE9之前版本中内存泄露问题
2018/04/28 Javascript
垃圾回收器的相关知识点总结
2018/05/13 Javascript
详解微信小程序调起键盘性能优化
2018/07/24 Javascript
Javascript生成器(Generator)的介绍与使用
2021/01/31 Javascript
[48:54]VGJ.T vs infamous Supermajor小组赛D组败者组第一轮 BO3 第二场 6.3
2018/06/04 DOTA
python将图片文件转换成base64编码的方法
2015/03/14 Python
Python爬虫爬取一个网页上的图片地址实例代码
2018/01/16 Python
Python使用numpy产生正态分布随机数的向量或矩阵操作示例
2018/08/22 Python
pycharm 解除默认unittest模式的方法
2018/11/30 Python
手把手教你Python yLab的绘制折线图的画法
2019/10/23 Python
python中什么是面向对象
2020/06/11 Python
米兰必去买手店排行榜首位:Antonioli
2016/09/11 全球购物
Made in Design德国:设计师家具、灯具和装饰
2019/10/31 全球购物
客服端调用EJB对象的几个基本步骤
2012/01/15 面试题
函授毕业个人自我评价
2014/02/20 职场文书
监察局领导班子四风问题整改措施思想汇报
2014/10/05 职场文书
践行三严三实心得体会
2014/10/13 职场文书
见义勇为事迹材料
2014/12/24 职场文书
培训计划通知
2015/07/15 职场文书