你理解的Array.prototype.forEach真的对吗?

Array.prototype.forEach

我们都知道,forEach() 方法对数组的每个元素执行一次给定的函数。它的语法也很简单:arr.forEach(callback(currentValue [, index [, array]])[, thisArg])

  • callback:为数组中每个元素执行的函数,该函数接收一至三个参数:

    • currentValue:数组中正在处理的当前元素。
    • index 可选,数组中正在处理的当前元素的索引。
    • array 可选,forEach() 方法正在操作的数组。
  • thisArg 可选参数。当执行回调函数 callback 时,用作 this 的值。
  • 返回值:undefined

常用用法:

const array1 = ['a', 'b', 'c'];
array1.forEach((element) => console.log(element)); // 输出:a,b,c

相比普通的 for 循环,forEach 无需自己控制循环条件,所以很多时候,forEach 方法被用来代替 for 循环来完成数组的遍历。

这个 forEach 的实现真的对吗?

因为很多时候,forEach 方法被用来代替 for 循环来完成数组的遍历,所以经常可以看见 forEach 的一些 js 实现,例如:

Array.prototype.forEachCustom = function (fn, context) {context = context || arguments[1];if (typeof fn !== 'function') {throw new TypeError(fn + 'is not a function');}for (let i = 0; i < this.length; i++) {fn.call(context, this[i], i, this);}
};

看起来没有问题,我们测试一下:

const items = ['item1', 'item2', 'item3'];
items.forEach((item) => {console.log(item); //  依次打印:item1,item2,item3
});
items.forEachCustom((item) => {console.log(item); // 依次打印:item1,item2,item3
});

好的,似乎没有问题,一切貌似都很完美。我们再测试下下面几个示例:

//  示例1
const items = ['', 'item2', 'item3', , undefined, null, 0];
items.forEach((item) => {console.log(item); //  依次打印:'',item2,item3,undefined,null,0
});
items.forEachCustom((item) => {console.log(item); // 依次打印:'',item2,item3,undefined,undefined,null,0
});
// 示例2
let arr = new Array(8);
arr.forEach((item) => {console.log(item); //  无打印输出
});
arr[1] = 9;
arr[5] = 3;
arr.forEach((item) => {console.log(item); //  打印输出:9 3
});
arr.forEachCustom((item) => {console.log(item); // 打印输出:daundefined 9 undefined*3  3 undefined*2
});

貌似发生了什么可怕的事儿,同样的数组经过 forEachCustom 和 forEach 调用,在打印出的值和值的数量上均有差别。看来我以为的并不真的就是我以为的。

追本溯源

怎么办呢?咱不妨去看看 ECMA 文档,看看 forEach 是怎么实现的:

我们可以发现,真正执行遍历操作的是第 8 条,通过一个 while 循环来实现,循环的终止条件是前面获取到的数组的长度(也就是说后期改变数组长度不会影响遍历次数),while 循环里,会先把当前遍历项的下标转为字符串,通过 HasProperty 方法判断数组对象中是否有下标对应的已初始化的项,有的话,获取对应的值,执行回调,没有的话,不会执行回调函数,而是直接遍历下一项

如此看来,forEach 不对未初始化的值进行任何操作(稀疏数组),所以才会出现示例 1 和示例 2 中自定义方法打印出的值和值的数量上均有差别的现象。那么,我们只需对前面的实现稍加改造,即可实现一个自己的 forEach 方法:

Array.prototype.forEachCustom = function (fn, context) {context = context || arguments[1];if (typeof fn !== 'function') {throw new TypeError(fn + 'is not a function');}let len = this.length;let k = 0;while (k < len) {if (this.hasOwnProperty(k)) {fn.call(context, this[k], k, this);}k++;}
};

再次运行示例 1 和示例 2 的测试用列,发现输出和原生 forEach 一致。

通过文档,我们还发现,在迭代前 while 循环的次数就已经定了,且执行了 while 循环,不代表就一定会执行回调函数,我们尝试在迭代时修改数组:

// 示例3
var words = ['one', 'two', 'three', 'four'];
words.forEach(function (word) {console.log(word); // one,two,four(在迭代过程中删除元素,导致three被跳过,因为three的下标已经变成1,而下标为1的已经被遍历了过)if (word === 'two') {words.shift();}
});
words = ['one', 'two', 'three', 'four']; // 重新初始化数组进行forEachCustom测试
words.forEachCustom(function (word) {console.log(word); // one,two,fourif (word === 'two') {words.shift();}
});
// 示例4
var arr = [1, 2, 3];
arr.forEach((item) => {if (item == 2) {arr.push(4);arr.push(5);}console.log(item); // 1,2,3(迭代过程中在末尾增加元素,并不会增加迭代次数)
});
arr = [1, 2, 3];
arr.forEachCustom((item) => {if (item == 2) {arr.push(4);arr.push(5);}console.log(item); // 1,2,3
});

番外篇

除了抛出异常以外,没有办法中止或跳出 forEach() 循环。如果你需要中止或跳出循环,forEach() 方法不是应当使用的工具。若你需要提前终止循环,你可以使用:

  • 一个简单的 for 循环
  • for...of / for...in 循环
  • Array.prototype.every()
  • Array.prototype.some()
  • Array.prototype.find()
  • Array.prototype.findIndex()

这些数组方法则可以对数组元素判断,以便确定是否需要继续遍历:

  • every()
  • some()
  • find()
  • findIndex()

总结

  • forEach 不对未初始化的值进行任何操作(稀疏数组);
  • 在迭代前,循环的次数就已经定了,且执行了循环,不代表就一定会执行回调函数;
  • 除了抛出异常以外,没有办法中止或跳出 forEach() 循环。

君墨学致:零基础学习前端需要掌握的技术和相关教程​zhuanlan.zhihu.com

作者:孤篷
链接:https://segmentfault.com/a/1190000030694113
来源:segmentfault
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

foreach判断最后一个_JavaScript很简单?那你理解的forEach真的对吗?相关推荐

  1. 毁掉一个广告很简单,放错一张图就行了

    作者:微微 全文共 5343 字 10 图,阅读需要 12 分钟 ---- / BEGIN / ---- 我们都知道,做一个成功的信息流投放不容易,需要兼顾创意.文案.图片.配色.版式.定向等多个方面 ...

  2. foreach判断最后一个_PHP 内核:foreach 是如何工作的(二)

    八重樱:PHP 内核:foreach 是如何工作的(一)​zhuanlan.zhihu.com PHP 5 内部数组指针和散列指针 PHP 5 中的数组有一个专用的 "内部数组指针" ...

  3. foreach判断最后一个_ArrayList集合为什么不能使用foreach增删改?

    点击上方"Java技术前线",选择"置顶或者星标" 与你一起成长 译者:奋斗的小程序员链接:http://suo.im/4XaI8Q 编程过程中常常需要使用到集 ...

  4. 补码(为什么按位取反再加一):告诉你一个其实很简单的问题

    ---------------------  作者:wenxinwukui234  来源:CSDN  原文:https://blog.csdn.net/wenxinwukui234/article/d ...

  5. 补码(为什么按位取反再加一):告诉你一个其实很简单的问题(转自醍醐灌顶)...

    首先,阅读这篇文章的你,肯定是一个在网上已经纠结了很久的读者,因为你查阅了所有你能查到的资料,然后他们都会很耐心的告诉你,补码:就是按位取反,然后加一.准确无误,毫无破绽.但是,你搜遍了所有俯拾即是而 ...

  6. 毁掉一个孩子很简单,原生家庭究竟有多重要

    Mo金社注:文章转载自Mo金社. 一个人和他的原生家庭有着千丝万缕的联系,而这种联系有可能影响他一生. 都说"父母是孩子的第一任老师",原生家庭对孩子的一生有着非常重要的影响.读完 ...

  7. java中为什么要用注解_java中的注解,真的很重要,你理解了嘛?

    这篇文章开始讲解java中的注解,在平时的开发当中我相信你或多或少的接触过注解.比如你可能都见过@override,它代表的就是一个注解.但是,为了更加清晰的去介绍注解,我还是先给出一个例子,让你能够 ...

  8. 一个很简单的问题:遍历int数组并删掉所有偶数

    一开始想的很简单,使用for循环和if判断进行遍历,如果是偶数则remove.后来发现和arraylist记混了,数组删除的话必须依次向前移一位,然后去掉最后一位. int[] line = {1,2 ...

  9. js判断最后一个字符是不是指定字符_结合简单的 JS 就可以让 CSS 也能做搜索

    是的,结合简单的 JS 代码就可以让 CSS 做当前页面的搜索引擎.效果大概就是这样: 其实呢这个是很早之前我就整了,用来给自己的一些无聊的 demo 或者瞎折腾玩的页面做索引搜索的.页面不多,但有时 ...

最新文章

  1. springboot 加载配置信息(静态)
  2. 13.13通过代码创建数据库和表
  3. java format 补足空格_11个简单的Java性能调优技巧
  4. 值得收藏的JSP连接mysql数据库的例子
  5. 设计模式—工厂模式之简单工厂模式
  6. Vue 凭什么成为 2020 年的一匹黑马
  7. java学习(166):socket服务端和客户端连接
  8. uboot将命令结构体单独存放在某个代码段的方法
  9. CV Code | 本周新出计算机视觉开源代码汇总(含自动驾驶目标检测、医学图像分割、风格迁移、语义分割、目标跟踪等)...
  10. flutter tab选项卡appbar下的选项卡
  11. unity 打开vs没有解决方案_VS找不到UnityEngine、UnityEngine.UI等引用的解决办法
  12. Web 探索之旅 | 第二部分第四课:数据库
  13. Dev C++下载安装
  14. 微信小程序文本溢出设置
  15. shopex手机版之触屏版,html5版,ShopEx手机触屏版V3.0重磅发布!优化用户界面,增强用户体验...
  16. 面向对象-当谈论面向对象的时候,我们到底在谈论什么?
  17. eclipsemaven服务器显示404,eclipse-HTTP状态404-请求的资源(/)不可用
  18. 长沙地铁一号线大客流运输组织优化研究
  19. CSS 样式实现单边阴影
  20. mit计算机33门课程_440多门免费在线编程和计算机科学课程,您可以在2月开始

热门文章

  1. java调用python的函数_java如何调用python的.py文件,以及如何执行里面的函数,和创建...
  2. Centos7 cdh5.14 安装
  3. nagios监控+pnp4出图
  4. Java was not the perfect solution for every pro...
  5. 基于富盛SBO程序开发框架的自动序列生成器
  6. 极客新闻——01、管理之善,在于让员工有机会试错
  7. 快速提升性能的SQL语句,建议收藏
  8. 记一次单机系统的性能优化:最后竟是 TCP 的锅
  9. 万字长文揭穿你,根本就不懂云原生!
  10. 微服务时代组件化和服务化的抉择