前言
初学JavaScript
的时候,知道有各种for
的时候,懵懵懂懂,也许是因为没有系统学习的缘故。现在我们把各种for
都挨个辨明。
一、for
创建一个循环,包含三个可选表达式。三个可选表达式在圆括号中,由分号分隔。后跟一个循环中执行的语句或块语句。
语法
for ([initialization]; [condition]; [final-expression]) statement复制代码
initialization
初始化语句。可写表达式、赋值语句、变量声明。
condition
循环条件表达式。如果表达式结果为true
,statement
会被执行。如果表达式结果为false
,那么执行流程跳到for
语句结构后面的第一条语句。不写表达式,就是永远为true
。
final-expression
每次循环的最后都要执行的表达式。执行时机是在下一次condition
的计算之前。
statement
只要condition
的结果为true
就会被执行的语句。多条语句使用块语句({...}
)来包含。没有语句执行,使用空语句(;
)。
示例
我想输出五个数字。
for (let i = 0; i < 5; i++) console.log(i);/*01234*/复制代码
另一种写法输出五个数字。可选的三个表达式,多行语句,需要使用{}
包含起来。
for (let i = 0; ; i++) { if (i >= 5) break; console.log(i);}/*01234*/复制代码
注意,如果不写条件表达式,就要确保循环体内能够跳出,防止死循环。break
可以跳出循环。
二、for...in
以任意顺序遍历一个对象的可枚举属性。对于每个枚举的属性,
...
部分都会被执行。
语法
for (variable in object) {...}复制代码
variable
每次迭代的时候,将对象的属性名分配给变量。
object
被迭代枚举的对象。
示例
我想输出对象里所有的属性和值。
let o = { a: 1, b: 2, c: 3};for (const v in o) { console.log(`o.${v} = ${o[v]}`);}/*o.a = 1o.b = 2o.c = 3*/复制代码
可以看见for...in
把所有的可枚举属性都枚举了出来,v
的类型是String
,所以访问当前遍历到的属性值使用了关联数组o[v]
的方式。
for...in
在遍历的时候,是以任意顺序遍历的。
let o = [];o[0] = 1;o['one'] = 2;o[2] = 3;for (const v in o) { console.log(`o[${v}] = ${o[v]}`);}/*o[0] = 1o[2] = 3o[one] = 2*/复制代码
因此当遇到对迭代访问顺序很重要的数组时,最好用整数索引。
我想累加数组所有的成员。
Array.prototype.age = 97;let o = [1,2];let sum = 0;for (const v in o) { sum += o[v]; console.log(`o[${v}] = ${o[v]}`);}console.log(`sum = ${sum}`);/*o[0] = 1o[1] = 2o[age] = 97sum = 100*/复制代码
很显然这里不符合我们的预期,因为for...in
循环语句将返回所有可枚举属性,包括非整数类型的名称和继承的那些。还会获取到原型链上的可枚举属性。
我只想累加自身所有属性。
Array.prototype.age = 97;let arr = [1, 2];let sum = 0;for (const v in arr) { if (arr.hasOwnProperty(v)) { sum += arr[v]; } console.log(`arr[${v}] = ${arr[v]}`);}console.log(`sum = ${sum}`);/*o[0] = 1o[1] = 2o[age] = 97sum = 3*/复制代码
如果你只要考虑对象本身的属性,而不是它的原型,那么使用Object.getOwnPropertyNames()
或执行Object.prototype.hasOwnProperty()
来确定某属性是否是对象本身的属性(也能使用propertyIsEnumerable
)。
三、Array.prototype.forEach()
对数组的每个元素执行一次提供的函数。返回值为
undefined
。
语法
Array.forEach(callback[, thisArg])复制代码
callback
为数组每个元素执行的函数,这个函数接受三个参数。
currentValue
数组中正在处理的当前元素值。
index
数组中正在处理的当前元素的索引。
array
forEach()
方法正在操作的数组。
thisArg
可选参数。当执行回调 函数时用作this
的值(参考对象)。
示例
我想输出所有元素。
function logArrayElements(element, index, array) { console.log(`a[${index}] = ${element}`);}[4, 2, 3].forEach(logArrayElements);/*a[0] = 4a[1] = 2a[2] = 3*/复制代码
forEcah()
会跳过已经删除或者为初始化的项(但不包括那些值为undefined
的项,例如在稀疏数组上)。
function logArrayElements(element, index, array) { console.log(`a[${index}] = ${element}`);}[4, , 3].forEach(logArrayElements);[1, undefined, 3].forEach(logArrayElements);/*a[0] = 4a[2] = 3a[0] = 1a[1] = undefineda[2] = 3*/复制代码
没有办法终止会跳出forEcah()
循环,除了抛出一个异常。
function logArrayElements(element, index, array) { console.log(`a[${index}] = ${element}`); break;}[1, 2, 3].forEach(logArrayElements);/*Uncaught SyntaxError: Illegal break statement at Array.forEach () at :5:11*/复制代码
使用return
也无法中止循环。
使用thisArg
,举个勉强的例子。通过自定义的add()
方法,计算所添加数组的和sum
和成员数count
。
function Counter() { this.sum = 0; this.count = 0;}Counter.prototype.add = function(array) { array.forEach(function(element) { this.sum += element; ++this.count; }, this);};let obj = new Counter();obj.add([1, 3, 5, 7]);console.log(obj.count); // 4 === (1+1+1+1)console.log(obj.sum); // 16 === (1+3+5+7)/*416*/复制代码
注意:如果使用箭头函数表达式传入函数参数,thisArg
参数会被忽略,因为箭头函数在词法上绑定了this
值。
如果数组在迭代时被修改了,则其他元素会被跳过。
let words = ["one", "two", "three", "four"];words.forEach(function(word) { console.log(word); if (word === "two") { words.shift(); }});/*onetwofour*/复制代码
当到达包含值"two"
的项时,整个数组的第一个项被移除了,这导致所有剩下的项前移了一个位置。因为元素"four"
现在在数组更前的位置,"three"
会被跳过。forEach()
不会在迭代之前创建数组的副本。
四、for...of
for...of
语句在可以迭代的对象(Array
、Map
、Set
、String
、TypedArray
、arguments
对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句。
语法
for (variable of iterable) { ...}复制代码
variable
在每次迭代中,将不同属性的值分配给变量。
iterable
被迭代枚举其属性的对象。
示例
迭代Array
let a = [10, 20, 30];for (let v of a) { console.log(v);}/*102030*/复制代码
迭代String
let s = 'Tang';for (let v of s) { console.log(v);}/*Tang*/复制代码
迭代arguments
(function() { for (let v of arguments) { console.log(v); }})(1, 2, 3);/*123*/复制代码
区别
无论是
for...in
还是for...of
语句都是迭代一些东西。它们之间的主要区别在于它们的迭代方式。
for...in
语句以原始插入顺序迭代对象的可枚举属性。
for...of
语句遍历可迭代对象定义要迭代的数据。
以下示例显示了与Array
一起使用时,for...of
循环和for...in
循环之间的区别。
Object.prototype.objCustom = function() {}; Array.prototype.arrCustom = function() {};let iterable = [3, 5, 7];iterable.foo = 'hello';for (let i in iterable) { console.log(i);}/*012fooarrCustomobjCustom*/for (let i in iterable) { if (iterable.hasOwnProperty(i)) { console.log(i); }}/*012foo*/for (let i of iterable) { console.log(i);}/*357*/复制代码