本文主要记录了在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即可:
> 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 上下文语句同步处理。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持。如有错误或未考虑完全的地方,望不吝赐教。
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。
更新日志
- 小骆驼-《草原狼2(蓝光CD)》[原抓WAV+CUE]
- 群星《欢迎来到我身边 电影原声专辑》[320K/MP3][105.02MB]
- 群星《欢迎来到我身边 电影原声专辑》[FLAC/分轨][480.9MB]
- 雷婷《梦里蓝天HQⅡ》 2023头版限量编号低速原抓[WAV+CUE][463M]
- 群星《2024好听新歌42》AI调整音效【WAV分轨】
- 王思雨-《思念陪着鸿雁飞》WAV
- 王思雨《喜马拉雅HQ》头版限量编号[WAV+CUE]
- 李健《无时无刻》[WAV+CUE][590M]
- 陈奕迅《酝酿》[WAV分轨][502M]
- 卓依婷《化蝶》2CD[WAV+CUE][1.1G]
- 群星《吉他王(黑胶CD)》[WAV+CUE]
- 齐秦《穿乐(穿越)》[WAV+CUE]
- 发烧珍品《数位CD音响测试-动向效果(九)》【WAV+CUE】
- 邝美云《邝美云精装歌集》[DSF][1.6G]
- 吕方《爱一回伤一回》[WAV+CUE][454M]