for in
在没有深度了解过for in的作用前,我只停留在它能够遍历对象键和数组下标的的层面上,那现在就来探究什么是一个对象中的Symbol类型键 (ES6之后新加的)和可枚举属性,
JS中的对象类型包括Object,Array,Function,Date,Math....
本文主要用Object和Array来进行实践
首先,先来看一段代码:
const arr = [5, 4, 3, 2, 1];
const obj = {
mark: "mark-v",
jack: "jack-v",
amy: "amy-v",
};
for (const i in arr) { // 输出 0 1 2 3 4
console.log(i);
}
for (const i in obj) { // 输出 mark jack amy
console.log(i);
}
我想必大家都很清楚输出的结果,如果是遍历数组则输出数组对应的下标,如果是遍历对象则输出对象的键名
继续提高难度,再看下面的代码:
const arr = [1, 2, 3, 4, 5];
arr.value = "array"; // 给数组对象上添加一个value属性
const s1 = Symbol("symbol1"); // 定义两个symbo类型的值
const s2 = Symbol("symbol2");
const obj = {
mark: "mark-v",
jack: "jack-v",
amy: "amy-v",
[s1]: "symbol1-v", // 给obj初始化这两个symbol键值
[s2]: "symbol2-v",
};
// 给obj原型上添加一个属性
Object.getPrototypeOf(obj).myProto = 'proto'
console.log(obj);
// 打印 {
// mark: 'mark-v',
// jack: 'jack-v',
// amy: 'amy-v',
// [Symbol(symbol1)]: 'symbol1-v',
// [Symbol(symbol2)]: 'symbol2-v'
// }
for (const i in arr) {
console.log(i); // 输出 0 1 2 3 4 value myProto
}
for (const i in obj) {
console.log(i); // 输出 mark jack amy myProto
}
从上面输出结果可以看出,无论是数组元素或者对象上的键值,都是可枚举属性,并且可以看出Symbol类型的键,虽然定义在了对象中,但是for in会跳过Symbol类型键的遍历
并且for in还会往原型链上遍历,那为什么遍历obj和arr都会输出 'myProto' 呢,那是因为obj是一个对象,默认的构造函数是Object(),而arr是一个数组对象,默认的构造函数是Array(),但是Array的prototype原型属性本质上也是一个对象,所以Array原型的原型就是Object上的prototype属性
Object.getPrototypeOf(obj) === Object.getPrototypeOf(Object.getPrototypeOf(arr)) // true
所以arr和obj的原型链是有交点了,也就解释了他们都能遍历到myProto
我们也可以单纯认为一个对象中的Symbol类型的键对应的属性不是一个可枚举属性,但是其实它依旧是一个可枚举属性,只是被跳过了而已,这段话说起来有点绕,不过往下看就清楚了。
既然for in的遍历绕不开可枚举属性,那什么是可枚举属性呢,MDN的解释如下:
可枚举属性是指那些内部"可枚举"标志设置为 true 的属性,对于通过直接的赋值和属性初始化的属性,该标识值默认为即为 true,对于通过 Object.defineProperty 等定义的属性,该标识值默认为 false。可枚举的属性可以通过 for...in 循环进行遍历(除非该属性名是一个 Symbol)。属性的所有权是通过判断该属性是否直接属于某个对象决定的,而不是通过原型链继承的。
从上面可以得出,
可枚举属性是对象内部那些"可枚举"标志设置为true的属性
通过字面量定义的属性,可枚举标识默认为true,对于通过Object.defineProperty等定义的属性,该标识默认为flase
for in循环只会对对象中的可枚举属性进行遍历,并且跳过属性名是Symbol类型的属性
for in只会遍历对象本身上面的可枚举属性
请看如下代码:
const s1 = Symbol("symbol1");
const s2 = Symbol("symbol2");
// 以下字面量初始化和赋值的属性可枚举标识都为true
const obj = { // 方式一
mark: "mark-v",
};
obj.jack = "jack-v" // 方式二
obj['amy'] = 'amy-v' // 方式三
Object.defineProperty(obj, "kong", {
value: "kong-v",
enumerable: false, // 可枚举标识,默认为 false
});
Object.defineProperty(obj, "john", {
value: "john-v",
enumerable: true, // 设置为 true
});
// 通过Object.defineProperty设置Symbol类型键
Object.defineProperty(obj, s1, {
value: "s1-v",
enumerable: false,
});
// 通过Object.defineProperty设置Symbol类型键,并且可枚举标识为true
Object.defineProperty(obj, s2, {
value: "s2-v",
enumerable: true,
});
console.log(obj);
// 打印 {
// mark: 'mark-v',
// jack: 'jack-v',
// amy: 'amy-v',
// john: 'john-v',
// [Symbol(symbol2)]: 's2-v'
// }
for (const i in obj) {
console.log(i); // 输出 mark jack amy john
}
上面代码我们首先是创建了一个obj对象,有一个初始化属性mark,然后通过字面量方式定义了两个新属性,并且通过Object.defineProperty定义了2个string类型和2个symbol类型的键值,都各有一个可枚举和不可枚举标识
可以看到console.log打印obj,只包含了可枚举的属性,也就是说可枚举标识为false的属性是不会出现在整个obj对象的打印内容中,也就验证了就算Symbol类型键的属性可枚举标识为true,也是会被for in跳过。
额外拓展
const arr = [1, 2, 3, 4, 5];
// 设置arr对象属性0的值为100,不可枚举,不可修改
Object.defineProperty(arr, 0, {
value: 100,
enumerable: false,
writable: false,
});
arr[0] = 1 // 尝试修改下标0的值
console.log(arr); // 打印 [ 100, 2, 3, 4, 5 ]
for (const i in arr) {
console.log(i); // 输出 1 2 3 4
}
可以看到数组的下标也是数组对象的属性,设置了下标0不可枚举和不可修改后,for in遍历不到下标0,并且也修改不了下标0的值,但是好玩的是打印数组还是会完整打印出每个元素。
for of
for...of
语句 在可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句
for of是ES6新出的一个用于遍历可迭代对象(实现了Iterator接口)的语法
// 迭代数组
const array = [10, 20, 30];
for (const value of array) {
console.log(value); // 输出 10 20 30
}
// 迭代String
const string = 'kong'
for (const s of string) {
console.log(s); // 输出 k o n g
}
// 迭代map
const map = new Map([["a", 1], ["b", 2], ["c", 3]]);
for (const item of map) {
console.log(item); // 输出 [ 'a', 1 ] [ 'b', 2 ] [ 'c', 3 ]
}
// 迭代set
const set = new Set([1, 1, 2, 2, 3, 3]);
for (let item of set) {
console.log(item); // 输出 1 2 3
}
关闭迭代器
对于for...of
的循环,可以由 break
, throw
或 return
终止。在这些情况下,迭代器关闭。
return情况
const array = [10, 20, 30];
for (const value of array) {
console.log(value); // 输出 10
return
}
// 下面代码不会执行,前面已经return
const string = 'kong'
for (const s of string) {
console.log(s);
}
throw情况
const array = [10, 20, 30];
for (const value of array) {
console.log(value); // 输出 10
throw new Error()
}
// 不执行下面代码,上面已经抛错
const string = "kong";
for (const s of string) {
console.log(s);
}
对于for...in
的循环,以上的中断方式也适用
总结
for...in适用于对象上面可枚举属性的遍历,并且只遍历非Symbol类型键以及对象自身以及原型链上的可枚举属性
for...of适用于实现了Iterator接口的对象(也称作可迭代对象)的遍历,其遍历方式由自身实现,例如对于数组是遍历其每个下标对应元素的值,对于Map遍历值为键值对组成的数组
各位大咖大牛,点个赞支持下,感谢!!!