写这篇文章真的很苦楚,因为我的心曾经不在这里。前前后后花了一周工夫才撬动键盘
循环集合令
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 间的区别,尤雨溪 有个奇妙的比喻:
- forEach 是你按程序让他们做什么事件
- map 是你拿着盒子,让他们将钱包扔进去,完结时返回一个新数组,外面有大家的钱包
- reduce 是拿着钱包,每个查看,把你和后面的综合都加起来,算总共多少钱
- filter 是过滤钱少于 100 快的,完结时返回一个新数组,外面都是钱大于 100 快的
总结
循环就这三板斧,一比照 for…in 和 for…of;二是比照 forEach 和 map;三是 ES6 中其余罕用 API
写不动了,覆灭吧
参考资料
- 彻底分清 JS 数组的 forEach() 和 map()
- MDN
- 如何形象地解释 JavaScript 中 map、foreach、reduce 间的区别?
系列文章
- 深刻了解 JavaScript——开篇
- 深刻了解 JavaScript——JavaScript 是什么
- 深刻了解 JavaScript——JavaScript 由什么组成
- 深刻了解 JavaScript——所有皆对象
- 深刻了解 JavaScript——Object(对象)
- 深刻了解 JavaScript——new 做了什么
- 深刻了解 JavaScript——Object.create
- 深刻了解 JavaScript——拷贝的机密
- 深刻了解 JavaScript——原型
- 深刻了解 JavaScript——继承
- 深刻了解 JavaScript——JavaScript 中的始皇
- 深刻了解 JavaScript——instanceof——找祖籍
- 深刻了解 JavaScript——Function
- 深刻了解 JavaScript——作用域
- 深刻了解 JavaScript——this 关键字
- 深刻了解 JavaScript——call、apply、bind 三大将
- 深刻了解 JavaScript——立刻执行函数(IIFE)
- 深刻了解 JavaScript——词法环境
- 深刻了解 JavaScript——执行上下文与调用栈
- 深刻了解 JavaScript——作用域 VS 执行上下文
- 深刻了解 JavaScript——闭包
- 深刻了解 JavaScript——防抖与节流
- 深刻了解 JavaScript——函数式编程
- 深刻了解 JavaScript——垃圾回收机制
- 深刻了解 JavaScript——数组