在 JS 语言中我们经常会遇到数组与对象的遍历场景,而且 JS 语言的灵活性导致我们不知道采用哪种写法会更好。本文做个总结:
1 数组的遍历
1.1 for 循环遍历
使用最基本的 for 循环来遍历数组,可以自定义循环的起始点、终止条件和步进值。
在 JavaScript 中,for 循环是一种常见的遍历数组的方式。例如:
const fruits = ["apple", "banana", "cherry", "orange"];
for(let i = 0; i < fruits.length; i++){
console.log(fruits[i]);
}
1.2 forEach 方法
除了 for 循环可以遍历数组外,JS 中还有一个方法也可以遍历数组:forEach ()。使用数组的 forEach 方法,它接受一个回调函数,对数组的每个元素执行该函数。
但此方法只支持 IE8 以上的浏览器。forEach () 方法需要一个函数作为参数。像 forEach () 中传入的函数,虽然由我们创建但不由我们调用,因此成为回调函数。数组中有几个元素,函数就会执行几次,每次执行时,浏览器会将遍历到的元素以实参的形式传递进来,所以我们可以定义形参,来读取这些内容。
var arr = ["中文", "英文", "日文", "法文"];
arr.forEach(function(a, b){ // 传入两个形参
console.log(1); // 会输出 4 次,因为 arr 里有 4 个元素
console.log(a); // 输出 arr 里的每个元素
console.log(b); // 输出 arr 里每个元素的索引
})
forEach () 方法迭代数组中的元素并为每个元素执行一次预定义函数。下面说明了 forEach () 方法的语法。
Array.forEach(callback [, thisArg]);
forEach () 方法有两个参数:
回调
forEach () 方法用于在每个元素上执行的回调函数。回调接受以下参数:
currentElement:是当前正在处理的数组元素。
index:当前元素在数组中的索引。
array:调用 forEach () 方法的数组。
索引和数组是可选的。
thisArg
thisArg 是执行回调时用作 this 的值。
请注意,forEach () 函数返回 undefined,因此,它不像 filter ()、map ()、some ()、every () 和 sort () 方法。
与 for 循环相比,forEach () 方法的一个限制是,我们不能使用 break 或 continue 语句来控制循环。我们要终止 forEach () 方法中的循环,必须在回调函数内抛出异常。
1.3 for…of 循环
for-of
循环,ES6 新增特性。有了 forEach 来遍历数组,似乎变得简洁了许多。
var arr = ["中文", "英文", "日文", "法文"];
for(var value of arr){
console.log(value);
}
注意for of
不仅可以遍历数组,还可以:
1、遍历字符串
2、遍历类数组对象
3、支持 Map 和 Set 对象遍历。
for of
遍历 map 对象举例:
var data = ["中文", "英文", "日文", "法文"];
for(const[index, item]of new Map(data.map((item, i)=>[i, item]))){
console.log(index, item)
}
1.4 map 方法
map
方法可以对数组中的每个元素执行一个函数,并返回一个新的数组。
map 和 forEach 几乎很像,但是也有它的不同之处,比如:
注意:map 是表示映射的,也就是一一对应,遍历完成之后会返回一个新的数组,但是不会修改原来的数组。
var a1 = ['a', 'b', 'c'];
var a2 = a1.map(function(item, key, arr) {
return item.toUpperCase();
});
console.log(a1);// ['a','b','c'];
console.log(a2); //['A','B','C'];
item:必填参数
,数组中正在处理的当前元素。
index:可选参数
,数组中正在处理的当前元素的索引。
arr:可选参数
,方法被调用的数组。也就是当前元素属于的数组对象。
map 方法会给原数组中的每个元素都按顺序调用一次 callback 函数。callback 每次执行后的返回值(包括 undefined
)组合起来形成一个新数组。 callback 函数只会在有值的索引上被调用;那些从来没被赋过值或者使用 delete 删除的索引则不会被调用。
callback 函数会被自动传入三个参数:数组元素,元素索引,原数组本身。
map 不修改调用它的原数组本身(当然可以在 callback 执行时改变原数组)。
注意:使用 map 方法处理数组时,数组元素的范围是在 callback 方法第一次调用之前就已经确定了。在 map 方法执行的过程中:原数组中新增加的元素将不会被 callback 访问到;若已经存在的元素被改变或删除了,则它们的传递到 callback 的值是 map 方法遍历到它们的那一时刻的值;而被删除的元素将不会被访问到。
1.5 filter 方法
filter
方法用于根据条件筛选出符合条件的数组元素,返回一个新数组。filter 有过滤的意思,也就说它就是一个过滤器。
注意:filter 它是遍历每一个元素,用每一个元素去匹配,如果返回 true,就会返回一个次数,最后将所有符合添加的全部选出。
var a1 =[1,2,3,4,5,6];
var a2 = a1.filter(function(item) {
return item <= 3;
});
console.log(a2); //[1,2,3];
1.6 reduce 方法
reduce
方法用于对数组中的所有元素进行累积操作,返回一个累积值。
reduce 从左到右依次遍历,一般用来做加减乘除运算使用。reduce ()
方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。下面举例:
var a1 =[1, 2, 3];
var total = a1.reduce(function(first, second) {
return first + second;
}, 0);
console.log(total) // 6
注意:就是 return first+second
其实相当于 return first+=second
;也就是说每次的 first 是上一次的和就是 function {} 后面的参数(值为0
),如果有值那么第一次加载的时候 first = 0;second = 1;如果没有值,first =1,second = 2;如果后面的参数是个字符串,那么就是会是字符串拼接。
1.7 some 和 every 方法
some
方法用于检查数组中是否至少有一个元素满足条件,而 every
方法用于检查数组中是否所有元素都满足条件。
some () 遍历数组,只要有一个以上的元素满足条件就返回 true,否则返回 false,退出循环。
arr.some(callback(element[, index[, array]])[, thisArg])
举例:
function isNumber(value){
return typeof value == 'number';
}
var a1 =[1, 2, 3];
console.log(a1.every(isNumber)); // true
var a2 =[1, '2', 3];
console.log(a2.every(isNumber)); // false
1.8 for…in 循环
for...in
循环用于遍历对象的属性,但也可以用于遍历数组。不过,不推荐在数组上使用 for...in
,因为它会包括数组的原型属性。
for...in
循环主要用于遍历对象的可枚举属性(包括继承的属性)。然而,它也可以遍历数组,但此时它遍历的是数组的索引(作为字符串)
,而不是数组的值。举例:
const array = ['apple', 'banana', 'orange'];
for (const key in array) {
console.log(key); // 输出:'0', '1', '2'(字符串形式的索引)
console.log(array[key]); // 输出数组的值
}
2 对象的遍历
2.1 Object.keys()
Object.keys () 方法会返回一个给定对象的自身 (不含继承的) 可枚举属性 (不含 Symbol 属性)组成的数组,数组中属性名的排列顺序和使用 for...in 循环遍历该对象时返回的顺序一致。它可以传入对象、字符串或数组等不同类型的数据,返回不同的结果。例如:传入对象时,返回包含对象可枚举属性和方法的数组;传入字符串时,返回索引值;传入数组时,也返回索引值
。
举例如下:
// 创建一个示例对象
const person = {
name: 'John',
age: 30,
city: 'New York'
};
// 使用Object.keys()获取对象的属性名数组
const keys = Object.keys(person);
// 遍历属性名数组并输出属性名和对应的值
keys.forEach(key => {
console.log(`${key}: ${person[key]}`);
});
// "name: John"
// "age: 30"
// "city: New York"
2.2 for in 循环
遍历对象自身的和继承的可枚举属性 (不含 Symbol 属性),使用时需注意过滤原型链上的属性。 for...in
是用来枚举对象的属性的,它遍历的是对象所有可遍历(enumerable)的属性,会跳过不可遍历的属性,不仅遍历对象自身的属性,还遍历继承的属性。每次循环都会将对象中存在的一个属性名赋值给变量,一直到对象中所有的属性都被枚举一遍为止。在多人协作开发项目时,可能会因为同事在代码里或引入的插件里对 Object.prototype 进行操作,导致遍历对象时出现意想不到的属性。可以利用 es5 的 Object.defineProperty
方法为属性添加限制,设置 enumerable 属性值为 false,让特定属性不可枚举,从而避免在遍历中出现。
2.2.1 基本示例
let myObject = {
name: "Alice",
age: 30,
hobby: "Reading"
};
for (let key in myObject) {
console.log(key + ": " + myObject[key]);
}
// "name: Alice"
// "age: 30"
// "hobby: Reading"
在这个例子中,for - in循环会遍历myObject对象的每一个可枚举属性。在每次循环中,key变量会被赋值为当前属性的名称,然后通过 myObject[key]
可以访问该属性的值。所以这个循环会输出对象myObject的所有属性名和对应的值。
2.2.2 遍历包含方法的对象
let person = {
firstName: "Bob",
lastName: "Smith",
getFullName: function () {
return this.firstName + " " + this.lastName;
}
};
for (let property in person) {
console.log(property + ": " + person[property]);
}
// "firstName: Bob"
// "lastName: Smith"
// 'getFullName: function () {
// return this.firstName + " " + this.lastName;
// }'
在这里,for - in
循环不仅会遍历对象的属性(firstName和lastName),还会遍历方法(getFullName)。当遍历到方法时,它会输出方法的代码内容。
2.2.3 继承情况下的遍历
当涉及对象继承时,for - in
循环会遍历对象自身的可枚举属性以及从原型链继承的可枚举属性。
let animal = {
type: "Mammal"
};
let dog = Object.create(animal);
dog.breed = "Labrador";
for (let key in dog) {
console.log(key + ": " + dog[key]);
}
// "breed: Labrador"
// "type: Mammal"
在这个例子中,dog
对象通过Object.create()从animal对象继承了type属性,并且自身有breed属性。for - in
循环会遍历dog对象的breed属性
和从animal对象继承的type
属性,输出它们的名称和对应的值。
需要注意的是,在实际应用中,如果只想遍历对象自身的属性,而不包括继承的属性,可以使用Object.hasOwnProperty()
方法来检查属性是否是对象自身的属性。例如:
let animal = {
type: "Mammal"
};
let dog = Object.create(animal);
dog.breed = "Labrador";
for (let key in dog) {
if (dog.hasOwnProperty(key)) {
console.log(key + ": " + dog[key]);
}
}
// "breed: Labrador"
2.3 Object.getOwnPropertyNames(obj)
Object.getOwnPropertyNames ()
方法返回一个数组,包含对象自身的所有属性名(包括不可枚举属性但不包括 Symbol 值作为名称的属性)组成的数组。可以通过数组的 forEach 方法来遍历这个数组,获取对象的属性名和对应的值。
2.3.1 基本示例
let myObject = {
name: "Eve",
age: 28,
[Symbol("secret")]: "hidden value"
};
let propertyNames = Object.getOwnPropertyNames(myObject);
for (let i = 0; i < propertyNames.length; i++) {
let propertyName = propertyNames[i];
console.log(propertyName + ": " + myObject[propertyName]);
}
// "name: Eve"
// "age: 28"
在这个例子中,Object.getOwnPropertyNames(myObject)返回一个包含对象myObject所有自身属性名的数组(不包括Symbol属性)。然后通过一个for循环遍历这个数组,对于每个属性名,通过myObject[propertyName]
获取属性值并打印出来。在这个例子中,会打印出name和age这两个属性的名称和对应的值。
2.3.2 遍历包含不可枚举属性的对象
let anotherObject = {};
Object.defineProperty(anotherObject, "id", {
value: 123,
enumerable: false
});
anotherObject.name = "Frank";
let anotherObjectPropertyNames = Object.getOwnPropertyNames(anotherObject);
console.log(anotherObjectPropertyNames);
// Array ["id", "name"]
这里通过Object.defineProperty
定义了一个不可枚举属性id,并添加了一个可枚举属性name。Object.getOwnPropertyNames
会返回包含id和name的数组,因为它返回的是对象自身的所有属性名,不管是否可枚举。如果使用Object.keys,它只会返回name,因为Object.keys只返回可枚举的属性名。
2.3.3 与函数对象结合使用
对于函数对象,Object.getOwnPropertyNames
也很有用。
function myFunction() {
this.x = 10;
}
myFunction.prototype.y = 20;
let functionObject = new myFunction();
let functionObjectPropertyNames = Object.getOwnPropertyNames(functionObject);
console.log(functionObjectPropertyNames);
// Array ["x"]
在这里,functionObject
是通过构造函数myFunction创建的。Object.getOwnPropertyNames
会返回包含x的数组,因为x是对象functionObject自身的属性,而不会返回y,因为y是在原型myFunction.prototype上定义的属性。
2.4 Reflect.ownKeys(obj)
Reflect.ownKeys () 方法返回一个数组,包含对象自身所有属性名组成的数组,包括不可枚举的属性和 Symbol 属性。这个方法在 ES2015 新增,是一种获取对象所有属性的强大方式。
2.4.1 基本示例
let myObject = {
name: "Grace",
age: 35,
[Symbol("secret")]: "This is a secret"
};
let keys = Reflect.ownKeys(myObject);
keys.forEach(key => {
if (typeof key === "string") {
console.log(`String property: ${key} - Value: ${myObject[key]}`);
} else if (typeof key === "symbol") {
console.log(`Symbol property: ${key.toString()} - Value: ${myObject[key]}`);
}
});
//"String property: name - Value: Grace"
//"String property: age - Value: 35"
// "Symbol property: Symbol(secret) - Value: This is a secret"
在这个例子中,Reflect.ownKeys(myObject)
返回一个包含对象myObject所有自身属性的数组,不管属性名是字符串还是Symbol,也不管是否可枚举。然后使用forEach循环遍历这个数组。在循环中,根据属性名的类型(字符串或Symbol),分别打印出属性名和对应的属性值。
2.4.2 包含不可枚举属性的对象
let nonEnumerableObject = {};
Object.defineProperty(nonEnumerableObject, "id", {
value: 100,
enumerable: false
});
nonEnumerableObject.name = "Henry";
let nonEnumerableKeys = Reflect.ownKeys(nonEnumerableObject);
console.log(nonEnumerableKeys);
// Array ["id", "name"]
这里通过Object.defineProperty
定义了一个不可枚举属性id,并添加了一个可枚举属性name。Reflect.ownKeys
会返回包含id和name的数组,因为它返回对象自身的所有属性,不受可枚举性的限制。
2.4.3 与类和原型链相关的示例
class MyClass {
constructor() {
this.instanceProperty = "Instance value";
}
}
MyClass.prototype.classProperty = "Class value";
let myInstance = new MyClass();
let instanceKeys = Reflect.ownKeys(myInstance);
console.log(instanceKeys);
// > Array ["instanceProperty"]
在这个例子中,myInstance是MyClass的一个实例。Reflect.ownKeys(myInstance)
会返回包含instanceProperty的数组,因为它只返回对象自身的属性,而不会返回在原型MyClass.prototype上定义的classProperty。这有助于我们清晰地分离对象自身的属性和从原型继承的属性。
2.5 Object.values()
Object.values ()
方法返回一个数组,只包含可枚举的属性值,不包括原型链上的属性。这个方法可以用于获取对象自身可枚举属性的值,方便对对象的属性值进行操作。
2.5.1 基本示例
const user = {
name: "Lucy",
age: 22,
city: "London"
};
const values = Object.values(user);
values.forEach((value, index) => {
console.log(`属性值 ${value} 对应的索引是 ${index}`);
});
// "属性值 Lucy 对应的索引是 0"
// "属性值 22 对应的索引是 1"
// "属性值 London 对应的索引是 2"
首先,Object.values(user)会获取对象user所有可枚举属性的值,返回一个数组[ "Lucy", 22, "London" ]。然后使用forEach方法遍历这个数组,value代表每个属性值,index是其在数组中的索引位置。这样就可以输出每个属性值以及它在属性值数组中的索引。
2.5.2 结合函数对象使用
const mathOperations = {
add: function (a, b) {
return a + b;
},
subtract: function (a, b) {
return a - b;
},
multiply: function (a, b) {
return a * b;
}
};
const operationValues = Object.values(mathOperations);
operationValues.forEach((operation) => {
console.log(`操作函数: ${operation.name}`);
});
// "操作函数: add"
// "操作函数: subtract"
// "操作函数: multiply"
这里Object.values(mathOperations)
获取对象mathOperations中函数属性的值,也就是函数本身。返回一个数组后,通过forEach遍历这个数组,operation代表每个函数。我们通过operation.name来输出每个函数的名称,如add、subtract、multiply
。
2.5.3 遍历嵌套对象
const nestedObject = {
outerProp1: "value1",
outerProp2: {
innerProp1: "value2",
innerProp2: "value3"
}
};
const nestedValues = Object.values(nestedObject);
nestedValues.forEach((value) => {
if (typeof value === "object") {
console.log("嵌套对象的值:", value);
const innerValues = Object.values(value);
innerValues.forEach((innerValue) => {
console.log("嵌套对象内部的值:", innerValue);
});
} else {
console.log("非嵌套对象的值:", value);
}
});
// "非嵌套对象的值:" "value1"
// "嵌套对象的值:" Object { innerProp1: "value2", innerProp2: "value3" }
// "嵌套对象内部的值:" "value2"
// "嵌套对象内部的值:" "value3"
首先,Object.values(nestedObject)
返回一个包含outerProp1
的值和outerProp2
这个对象的数组。然后在forEach循环中,对于非对象类型的值(如outerProp1
的值)直接输出。对于对象类型的值(如outerProp2
),再使用Object.values
获取其内部属性的值并输出,这样就可以遍历嵌套对象中的所有属性值。
2.6 Object.entries()
Object.entries ()
方法返回一个数组,包含给定对象自身可枚举属性的 [key, value]
对。这个数组的排列顺序和使用 for...in
循环遍历该对象时返回的顺序一致,但与 for-in 不同的是,它不会枚举原型链上的属性。
2.6.1 基本示例
const book = {
title: "JS",
author: "John Doe",
year: 2023
};
const entries = Object.entries(book);
entries.forEach(([key, value]) => {
console.log(`属性名: ${key}, 属性值: ${value}`);
});
// "属性名: title, 属性值: JS"
// "属性名: author, 属性值: John Doe"
// "属性名: year, 属性值: 2023"
Object.entries(book)
返回一个二维数组,其中每个子数组包含两个元素:属性名和对应的属性值。
2.6.2 与数组的 map 方法结合使用
const car = {
brand: "Toyota",
model: "Corolla",
color: "Blue"
};
const newArray = Object.entries(car).map(([key, value]) => {
return `${key}: ${value}`;
});
console.log(newArray);
// Array ["brand: Toyota", "model: Corolla", "color: Blue"]
这里,Object.entries(car)
获取汽车对象car的属性名 - 属性值对数组。然后使用map方法对这个数组进行转换,将每个属性名和属性值组合成一个格式化的字符串(如brand: Toyota),最后将这些字符串存储在newArray中并打印出来。
2.7 Object.getOwnPropertySymbols()
Object.getOwnPropertySymbols ()
方法返回一个数组,包含对象自身的所有 Symbol
属性键。这个方法可以用于获取对象的 Symbol
属性,方便对这些特殊类型的属性进行操作。
2.7.1 基本示例
const myObject = {
name: "Tom",
[Symbol("id")]: 123
};
const symbols = Object.getOwnPropertySymbols(myObject);
symbols.forEach((symbol) => {
console.log(`Symbol属性: ${symbol.toString()},值为: ${myObject[symbol]}`);
});
// "Symbol属性: Symbol(id),值为: 123"
在这个例子中,Object.getOwnPropertySymbols(myObject)
返回一个包含对象myObject所有自身Symbol属性的数组。这里只有一个Symbol("id")属性。然后通过forEach循环遍历这个数组,对于每个Symbol属性,通过myObject[symbol]获取其对应的值,并打印出Symbol属性的字符串表示和它的值。
2.7.2 结合普通属性的对象
const anotherObject = {
age: 30,
[Symbol("secret")]: "This is a secret"
};
const symbolsOfAnotherObject = Object.getOwnPropertySymbols(anotherObject);
console.log("Symbol属性列表:");
symbolsOfAnotherObject.forEach((symbol) => {
console.log(symbol.toString());
});
console.log("普通属性列表:");
const propertyNames = Object.keys(anotherObject);
propertyNames.forEach((propertyName) => {
console.log(propertyName);
});
// "Symbol属性列表:"
// "Symbol(secret)"
// "普通属性列表:"
// "age"
这里Object.getOwnPropertySymbols(anotherObject)
获取对象的Symbol属性数组,然后分别打印出这些Symbol属性的字符串表示。接着使用Object.keys获取普通属性数组并打印出普通属性的名称,这样就可以清晰地分开查看对象的Symbol属性和普通属性。