深入理解JavaScript——循环都来这儿

简介: 写这篇文章真的很痛苦,因为我的心已经不在这里。前前后后花了一周时间才撬动键盘

循环集合令


JavaScript 中有各种循环,如 for ,for(reverse),for...in,for...of,forEach,map 等,这些循环各有什么特点呢?


for 循环

这是最常见的循环

for (var i = 0; i < 10; i++) {
    console.log(i)
}


它的特点是最常见,缺点是可读性差


for(reverse) 循环

顾名思义,它是 for 循环的翻转版

for (var i = 10; i > 0; i--) {
    console.log(i)
}

同样的可读性差


for...in

for 循环可以以任意顺序迭代一个对象的除 Symbol 以外的可枚举属性,包括继承的可枚举属性


它有两个特点,一是迭代的是对象,二是循环目标是可枚举属性,包括继承的属性

// 例子1:迭代对象
var obj = {a: 1, b: 2, c: 3}
for (var key in obj) {
    console.log("obj." + key + " = " + obj[key])
}
// obj.a = 1
// obj.b = 2
// obj.c = 3


// 例子2:迭代可枚举属性
Object.prototype.sayHello = function() { console.log('hello') }
// 数组也属于对象,数组也能用对象的方法
const iterable = [1, 2, 3]
iterable.name = 'hello'
for (let key in iterable) {
    console.log("key&value: " + key + " = " + iterable[key]) 
}
// key&value: 0 = 1
// key&value: 1 = 2
// key&value: 2 = 3
// key&value: name = hello
// key&value: sayHello = function() { console.log('hello') }


它的使用场景是需要检查其中的任何键是否为某值的情况


我们在 拷贝的秘密 中曾手写过简易深拷贝:

function deepClone(source) {
    if (typeof source !== 'object' || source === null) {
        return source
    }
    let target = Array.isArray(source) ? [] : {}
    for (let prop in target) {
        if (source.hasOwnProperty(prop)) {
            target[prop] = typeof source[prop] === 'object' ? deepClone(source[prop]) : source[prop]
        }
    }
    return target
}


通过 for...in 遍历所有可枚举属性,然后通过 hasOwnProperty 过滤非自身属性的属性,从而拿到所有自身可枚举的属性,完成深拷贝

与之相对应的是 for...of


for...of

for...in 是 ES5 是为了解决遍历对象的 key 而新出的 API,而 for...of 是 ES6 时支持的特性,它的用途是遍历可迭代的对象(包括 Array、Map、Set、String、arguments等)

以 for...in 中的例子2为例:


Object.prototype.sayHello = function() { console.log('hello') }
const iterable = [1, 2, 3]
iterable.name = 'hello'
for (let i of iterable) {
  console.log(i); // 3, 5, 7
}


如果说 for...in 是为了拿到对象的 key(因为 value 在 for 循环中都能获得),那么 for...of 就是更方便拿到对象的 value


forEach

ES5 时数组新增的 API,能对数组的每个元素执行一次给定的函数。遍历时,不能被 break 或 return 提前结束循环


先看看它的参数,共三点:

  • element:当前元素
  • index:当前元素的索引
  • array:原数组
const array1 = ['a', 'b', 'c'];
array1.forEach((str, i, origin) => {
     console.log(`${i}: ${str} / ${origin}`);
});
// 0: a / a,b,c
// 1: b / a,b,c
// 2: c / a,b,c


被调用时,不会改变原数组(重点)

const array1 = ['a', 'b', 'c'];
array1.forEach((element) => {
    element = element + 1
});
console.log(array1) // [a, b, c]


但往往会在项目开发时遇到这类例子:

const arr = [{
    name: 'johan',
    age: 29
}, {
    name: 'elaine',
    age: 29
}]
arr.forEach(ele => {
    if (ele.name === 'johan') {
        ele.age = 22
    }
})
console.log(arr) // [{name: 'johan', age: 22}, {name: 'elaine', age: 29}]


原数组被改变了,为什么呢?

因为虽然调用 forEach 不会改变原数组,但是在 callbackFn 可能会改变 element(当前对象)


在第一个例子中,因为当前元素是基本类型,所以对它赋值是原数组是无效的,而当当前元素是引用类型时,情况就不同的,引用类型存在堆内存中,共享一个引用地址,当改变指时,原数组也就发生了变化,可以回顾下拷贝的秘密


map

ES6 时新增 API。此方法能创建一个新数组。这个新数组由原数组中的每个元素都调用一次提供的函数后的返回值组成


const array1 = [1, 4, 9, 16];
const map1 = array1.map(x => x * 2);
console.log(map1); // [2, 8, 18, 32]


如果说 for...in 常和 for...of 被人拿出来比较,那么 forEach 的比较对象就是 map

例如 forEach 没有返回值,而 map 会返回一个新数组;forEach 偶尔会改变原数组(在callbackFn 改变当前对象时),而因为 map 返回的是一个新数组,所以永远不会改变原数组(所以在函数式编程中常会使用到 map,它就是标准的纯函数)


衍生一道面试题

["1","2","3"].map(parseInt)


为什么这题的答案是 [1, NaN, NaN] 呢?

parseInt 的语法是 parseInt(string, radix)

也就是说

// parseInt(string, radix) => map(parseInt(value, index))
// 也就是说它的迭代过程是:
// 第一次迭代:index is 0: parseInt("1", 0)  // 1
// 第二次迭代:index is 1: parseInt("2", 1)  // NaN
// 第三次迭代:index is 2: parseInt("3", 2)  // NaN


radix 表示进制的基数,范围是2~36,一般常见的是二进制、八进制、十进制、十六进制

当二进制时,除了“0、1“外,其他数字都不是有效二进制数字


测速比较


一些屌丝面试题会问这些循环方法速度排序。太搞笑了,又不是造火箭,问这些的目的是什么呢?

不过,写都写到这里了,不妨测一下


例子:

const million = 10000000;
const arr = Array(million);
console.time('timer')
for (let i = 0; i < arr.length; i++) {} // for
for (let i = arr.length; i > 0; i--) {}  // for(reverse)
for (const v in arr) {} // for...in
for (const v of arr) {} // for...of
arr.forEach(v => v) // forEach
arr.map(v => v) // map
console.timeEnd('timer')


衍生


在知乎中有人提问:如何形象地解释 JavaScript 中 map、foreach、reduce 间的区别[1]尤雨溪[2] 有个巧妙的比喻:

  • forEach 是你按顺序让他们做什么事情
  • map 是你拿着盒子,让他们将钱包扔进去,结束时返回一个新数组,里面有大家的钱包
  • reduce 是拿着钱包,每个检查,把你和前面的综合都加起来,算总共多少钱
  • filter 是过滤钱少于100快的,结束时返回一个新数组,里面都是钱大于100快的


总结


循环就这三板斧,一对比 for...in 和 for...of ;二是对比 forEach 和 map;三是 ES6 中其他常用 API


写不动了,毁灭吧


参考资料



[1] 如何形象地解释 JavaScript 中 map、foreach、reduce 间的区别: https://www.zhihu.com/question/24927450/answer/29478982

[2] 尤雨溪: https://www.zhihu.com/people/evanyou

[3] MDN: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach

[4] 如何形象地解释 JavaScript 中 map、foreach、reduce 间的区别?: https://www.zhihu.com/question/24927450/answer/29478982

相关文章
|
3月前
|
JavaScript 前端开发
JS循环for、for...of、for...in
本文介绍了JavaScript中不同的循环语句,包括传统的`for`循环、`for...of`循环用于遍历数组和类数组对象、`for...in`循环用于遍历对象的属性,并通过示例代码展示了它们的用法和区别。
51 6
JS循环for、for...of、for...in
|
3月前
|
JavaScript 前端开发
JavaScript基础知识-流程控制之while循环
这篇文章介绍了JavaScript中的while循环和do...while循环的基础知识,并通过一个实际案例演示了如何使用while循环计算投资增长到特定金额所需的年数。
55 2
JavaScript基础知识-流程控制之while循环
|
2月前
|
JavaScript 前端开发
js循环有几种
js循环有几种
32 0
|
4月前
|
JavaScript 前端开发
JavaScript中有哪几种循环?他们的运用场景在哪?
JavaScript中有哪几种循环?他们的运用场景在哪?
|
4月前
|
JavaScript 前端开发 索引
js的循环中foreach、for in和for of的区别
js的循环中foreach、for in和for of的区别
159 0
|
25天前
|
JavaScript
js动画循环播放特效源码(上班族的一天)
js动画循环播放特效是一段实现了包含形象的卡通小人吃、睡、电脑工作的网页动画,js循环动画,简单的画面设计。非常丝滑有意思,欢迎对此代码感兴趣的朋友前来下载参考。
27 2
|
3月前
|
前端开发 JavaScript
前端基础(八)_JavaScript循环(for循环、for-in循环、for-of循环、while、do-while 循环、break 与 continue)
本文介绍了JavaScript中的循环语句,包括for循环、for-in循环、for-of循环、while循环、do-while循环以及break和continue的使用。
67 1
前端基础(八)_JavaScript循环(for循环、for-in循环、for-of循环、while、do-while 循环、break 与 continue)
|
3月前
|
JavaScript 前端开发
JavaScript基础知识-forEach循环
关于JavaScript基础知识中forEach循环的介绍。
46 1
JavaScript基础知识-forEach循环
|
3月前
|
JavaScript 前端开发 索引
|
2月前
|
JavaScript
自动循环提交js
自动循环提交js
18 0