javascript的五种循环,作为程序员,要根据场景和性能作出选择

几种遍历方法中for执行最快,它没有任何额外的函数调用栈和上下文。但在实际开发中我们要结合语义化、可读性和程序性能,去选择究竟使用哪种方案。下面来看for , foreach , map , for...in , for...of五种方法现场battle。

for语法

我是最早出现的一方遍历语句,在座的各位需称我一声爷爷。我能满足开发人员的绝大多数的需求。

// 遍历数组

letarr = [1,2,3];

for(leti =0;i < arr.length;i++){

console.log(i)// 索引,数组下标

console.log(arr[i])// 数组下标所对应的元素

}

// 遍历对象

letprofile = {name:"April",nickname:"二十七刻",country:"China"};

for(leti =0, keys=Object.keys(profile); i < keys.length;i++){

console.log(keys[i])// 对象的键值

console.log(profile[keys[i]])// 对象的键对应的值

}

// 遍历字符串

letstr ="abcdef";

for(leti =0;i < str.length ;i++){

console.log(i)// 索引 字符串的下标

console.log(str[i])// 字符串下标所对应的元素

}

// 遍历DOM 节点

letarticleParagraphs =document.querySelectorAll('.article > p');

for(leti =0;i<articleParagraphs.length;i++){

articleParagraphs[i].classList.add("paragraph");

// 给class名为“article”节点下的 p 标签添加一个名为“paragraph” class属性。

}

forEach

我是ES5版本发布的。按升序为数组中含有效值的每一项执行一次 callback 函数,那些已删除或者未初始化的项将被跳过(例如在稀疏数组上)。我是 for 循环的加强版。

// 遍历数组

letarr = [1,2,3];

arr.forEach(i=>console.log(i))

// logs 1

// logs 2

// logs 3

// 直接输出了数组的元素

//遍历对象

letprofile = {name:"April",nickname:"二十七刻",country:"China"};

letkeys =Object.keys(profile);

keys.forEach(i=>{

console.log(i)// 对象的键值

console.log(profile[i])// 对象的键对应的值

})

map

我也是ES5版本发布的,我可以创建一个新数组,新数组的结果是原数组中的每个元素都调用一次提供的函数后的返回值。

letarr = [1,2,3,4,5];

letres = arr.map(i=>i * i);

console.log(res)// logs [1, 4, 9, 16, 25]

for...in枚举

我是ES5版本发布的。以任意顺序遍历一个对象的除Symbol以外的可枚举属性。

// 遍历对象

letprofile = {name:"April",nickname:"二十七刻",country:"China"};

for(letiinprofile){

letitem = profile[i];

console.log(item)// 对象的键值

console.log(i)// 对象的键对应的值

// 遍历数组

letarr = ['a','b','c'];

for(letiinarr){

letitem = arr[i];

console.log(item)// 数组下标所对应的元素

console.log(i)// 索引,数组下标

// 遍历字符串

letstr ="abcd"

for(letiinstr){

letitem = str[i];

console.log(item)// 字符串下标所对应的元素

console.log(i)// 索引 字符串的下标

}

for...of迭代

我是ES6版本发布的。在可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句。

// 迭代数组数组

letarr = ['a','b','c'];

for(letitemofarr){

console.log(item)

}

// logs 'a'

// logs 'b'

// logs 'c'

// 迭代字符串

letstr ="abc";

for(letvalueofstr) {

console.log(value);

}

// logs 'a'

// logs 'b'

// logs 'c'

// 迭代map

letiterable =newMap([["a",1], ["b",2], ["c",3]]

for(letentryofiterable) {

console.log(entry);

}

// logs ["a", 1]

// logs ["b", 2]

// logs ["c", 3]

// 迭代map获取键值

for(let[key, value]ofiterable) {

console.log(key)

console.log(value);

}

// 迭代set

letiterable =newSet([1,1,2,2,3,3,4]);

for(letvalueofiterable) {

console.log(value);

}

// logs 1

// logs 2

// logs 3

// logs 4

// 迭代 DOM 节点

letarticleParagraphs =document.querySelectorAll('.article > p');

for(letparagraphofarticleParagraphs) {

paragraph.classList.add("paragraph");

// 给class名为“article”节点下的 p 标签添加一个名为“paragraph” class属性。

}

// 迭代arguments类数组对象

(function(){

for(letargumentofarguments) {

console.log(argument);

}

})(1,2,3);

// logs:

// 1

// 2

// 3

// 迭代类型数组

lettypeArr =newUint8Array([0x00,0xff]);

for(letvalueoftypeArr) {

console.log(value);

}经过第一轮的自我介绍和技能展示后,我们了解到:

for语句是最原始的循环语句。定义一个变量i(数字类型,表示数组的下标),按照一定的条件,对i进行循环累加。条件通常为循环对象的长度,当超过长度就停止循环。因为对象无法判断长度,所以搭配Object.keys()使用。forEach ES5 提出。自称是for语句的加强版,可以发现它比for语句在写法上简单了很多。但是本质上也是数组的循环。forEach每个数组元素执行一次 callback 函数。也就是调用它的数组,因此,不会改变原数组。返回值是undefine。map ES5 提出。给原数组中的每个元素都按顺序调用一次 callback 函数。生成一个新数组,不修改调用它的原数组本身。返回值是新的数组。for...in ES5 提出。遍历对象上的可枚举属性,包括原型对象上的属性,且按任意顺序进行遍历,也就是顺序不固定。遍历数组时把数组的下标当作键值,此时的i是个字符串型的。它是为遍历对象属性而构建的,不建议与数组一起使用。for...of ES6 提出。只遍历可迭代对象的数据。

能力甄别

作为一个程序员,仅仅认识他们是远远不够的,在实际开发中鉴别他们各自的优缺点。因地制宜的使用他们,扬长避短。从而提高程序的整体性能才是能力之所在。

关于跳出循环体

在循环中满足一定条件就跳出循环体,或者跳过不符合条件的数据继续循环其它数据。是经常会遇到的需求。常用的语句是break 与 continue。

简单的说一下二者的区别,就当复习好了。

break语句是跳出当前循环,并执行当前循环之后的语句;continue语句是终止当前循环,并继续执行下一次循环;注意:forEach 与map 是不支持跳出循环体的,其它三种方法均支持。

原理 :查看forEach实现原理,就会理解这个问题。

Array.prototype.forEach(callbackfn[,thisArg]{

}

传入的function是这里的回调函数。在回调函数里面使用break肯定是非法的,因为break只能用于跳出循环,回调函数不是循环体。

在回调函数中使用return,只是将结果返回到上级函数,也就是这个for循环中,并没有结束for循环,所以return也是无效的。

map() 同理。

map()链式调用

map() 方法是可以链式调用的,这意味着它可以方便的结合其它方法一起使用。例如:reduce(), sort(), filter() 等。但是其它方法并不能做到这一点。forEach()的返回值是undefined,所以无法链式调用。

// 将元素乘以本身,再进行求和。

letarr = [1,2,3,4,5];

letres1 = arr.map(item=>item * item).reduce((total, value) =>total + value);

console.log(res1)// logs 55 undefined"for...in会遍历出原型对象上的属性

Object.prototype.objCustom =function(){};

Array.prototype.arrCustom =function(){};

vararr = ['a','b','c'];

arr.foo ='hello

for (var i in arr) {

console.log(i);

}

// logs

// 0

// 1

// 2

// foo

// arrCustom

// objCustom

然而在实际的开发中,我们并不需要原型对象上的属性。这种情况下我们可以使用hasOwnProperty() 方法,它会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)。如下:

Object.prototype.objCustom =function(){};

Array.prototype.arrCustom =function(){};

vararr = ['a','b','c'];

arr.foo ='hello

for (var i in arr) {

if (arr.hasOwnProperty(i)) {

console.log(i);

}

}

// logs

// 0

// 1

// 2

// foo

// 可见数组本身的属性还是无法摆脱。此时建议使用 forEach

对于纯对象的遍历,选择for..in枚举更方便;对于数组遍历,如果不需要知道索引for..of迭代更合适,因为还可以中断;如果需要知道索引,则forEach()更合适;对于其他字符串,类数组,类型数组的迭代,for..of更占上风更胜一筹。但是注意低版本浏览器的适配性。

性能

有兴趣的读者可以找一组数据自行测试,文章就直接给出结果了,并做相应的解释。

for>for-of> forEach > map >for-in

for 循环当然是最简单的,因为它没有任何额外的函数调用栈和上下文;for...of只要具有Iterator接口的数据结构,都可以使用它迭代成员。它直接读取的是键值。forEach,因为它其实比我们想象得要复杂一些,它实际上是array.forEach(function(currentValue, index, arr), thisValue)它不是普通的 for 循环的语法糖,还有诸多参数和上下文需要在执行的时候考虑进来,这里可能拖慢性能;map() 最慢,因为它的返回值是一个等长的全新的数组,数组创建和赋值产生的性能开销很大。for...in需要穷举对象的所有属性,包括自定义的添加的属性也能遍历到。且for...in的key是String类型,有转换过程,开销比较大。

总结

在实际开发中我们要结合语义化、可读性和程序性能,去选择究竟使用哪种方案?

如果你需要将数组按照某种规则映射为另一个数组,就应该用 map。如果你需要进行简单的遍历,用 forEach 或者 for of。如果你需要对迭代器进行遍历,用 for of。如果你需要过滤出符合条件的项,用 filterr。如果你需要先按照规则映射为新数组,再根据条件过滤,那就用一个 map 加一个 filter。

总之,因地制宜,因时而变。千万不要因为过分追求性能,而忽略了语义和可读性。

原文地址:https://www.toutiao.com/article/6862225186939372039/

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 目录 连接 连接池产生原因 连接池实现原理 小结 TEMPERANCE:Eat not to dullness;drink not to elevation.节制
摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 一个优秀的工程师和一个普通的工程师的区别,不是满天飞的架构图,他的功底体现在所写的每一行代码上。-- 毕玄 1. 命名风格 【书摘】类名用 UpperCamelC
今天犯了个错:“接口变动,伤筋动骨,除非你确定只有你一个人在用”。哪怕只是throw了一个新的Exception。哈哈,这是我犯的错误。一、接口和抽象类类,即一个对象。先抽象类,就是抽象出类的基础部分,即抽象基类(抽象类)。官方定义让人费解,但是记忆方法是也不错的 —包含抽象方法的类叫做抽象类。接口
Writer :BYSocket(泥沙砖瓦浆木匠)微 博:BYSocket豆 瓣:BYSocketFaceBook:BYSocketTwitter :BYSocket一、引子文件,作为常见的数据源。关于操作文件的字节流就是 —FileInputStream&amp;FileOutputStream。
作者:泥沙砖瓦浆木匠网站:http://blog.csdn.net/jeffli1993个人签名:打算起手不凡写出鸿篇巨作的人,往往坚持不了完成第一章节。交流QQ群:【编程之美 365234583】http://qm.qq.com/cgi-bin/qm/qr?k=FhFAoaWwjP29_Aonqz
本文目录 线程与多线程 线程的运行与创建 线程的状态 1 线程与多线程 线程是什么? 线程(Thread)是一个对象(Object)。用来干什么?Java 线程(也称 JVM 线程)是 Java 进程内允许多个同时进行的任务。该进程内并发的任务成为线程(Thread),一个进程里至少一个线程。 Ja
Writer :BYSocket(泥沙砖瓦浆木匠)微 博:BYSocket豆 瓣:BYSocketFaceBook:BYSocketTwitter :BYSocket在面向对象编程中,编程人员应该在意“资源”。比如?1String hello = &quot;hello&quot;; 在代码中,我们
摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 这是泥瓦匠的第103篇原创 《程序兵法:Java String 源码的排序算法(一)》 文章工程:* JDK 1.8* 工程名:algorithm-core-le
摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 目录 一、父子类变量名相同会咋样? 有个小故事,今天群里面有个人问下面如图输出什么? 我回答:60。但这是错的,答案结果是 40 。我知错能改,然后说了下父子类变
作者:泥瓦匠 出处:https://www.bysocket.com/2021-10-26/mac-create-files-from-the-root-directory.html Mac 操作系统挺适合开发者进行写代码,最近碰到了一个问题,问题是如何在 macOS 根目录创建文件夹。不同的 ma
作者:李强强上一篇,泥瓦匠基础地讲了下Java I/O : Bit Operation 位运算。这一讲,泥瓦匠带你走进Java中的进制详解。一、引子在Java世界里,99%的工作都是处理这高层。那么二进制,字节码这些会在哪里用到呢?自问自答:在跨平台的时候,就凸显神功了。比如说文件读写,数据通信,还
1 线程中断 1.1 什么是线程中断? 线程中断是线程的标志位属性。而不是真正终止线程,和线程的状态无关。线程中断过程表示一个运行中的线程,通过其他线程调用了该线程的 方法,使得该线程中断标志位属性改变。 深入思考下,线程中断不是去中断了线程,恰恰是用来通知该线程应该被中断了。具体是一个标志位属性,
Writer:BYSocket(泥沙砖瓦浆木匠)微博:BYSocket豆瓣:BYSocketReprint it anywhere u want需求 项目在设计表的时候,要处理并发多的一些数据,类似订单号不能重复,要保持唯一。原本以为来个时间戳,精确到毫秒应该不错了。后来觉得是错了,测试环境下很多一
纯技术交流群 每日推荐 - 技术干货推送 跟着泥瓦匠,一起问答交流 扫一扫,我邀请你入群 纯技术交流群 每日推荐 - 技术干货推送 跟着泥瓦匠,一起问答交流 扫一扫,我邀请你入群 加微信:bysocket01
Writer:BYSocket(泥沙砖瓦浆木匠)微博:BYSocket豆瓣:BYSocketReprint it anywhere u want.文章Points:1、介绍RESTful架构风格2、Spring配置CXF3、三层初设计,实现WebService接口层4、撰写HTTPClient 客户
Writer :BYSocket(泥沙砖瓦浆木匠)什么是回调?今天傻傻地截了张图问了下,然后被陈大牛回答道“就一个回调…”。此时千万个草泥马飞奔而过(逃哈哈,看着源码,享受着这种回调在代码上的作用,真是美哉。不妨总结总结。一、什么是回调回调,回调。要先有调用,才有调用者和被调用者之间的回调。所以在百
Writer :BYSocket(泥沙砖瓦浆木匠)一、什么大小端?大小端在计算机业界,Endian表示数据在存储器中的存放顺序。百度百科如下叙述之:大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加
What is a programming language? Before introducing compilation and decompilation, let&#39;s briefly introduce the Programming Language. Programming la
Writer :BYSocket(泥沙砖瓦浆木匠)微 博:BYSocket豆 瓣:BYSocketFaceBook:BYSocketTwitter :BYSocket泥瓦匠喜欢Java,文章总是扯扯Java。 I/O 基础,就是二进制,也就是Bit。一、Bit与二进制什么是Bit(位)呢?位是CPU
Writer:BYSocket(泥沙砖瓦浆木匠)微博:BYSocket豆瓣:BYSocket一、前言 泥瓦匠最近被项目搞的天昏地暗。发现有些要给自己一些目标,关于技术的目标:专注很重要。专注Java 基础 + H5(学习) 其他操作系统,算法,数据结构当成课外书博览。有时候,就是那样你越是专注方面越