前端面试题库 (面试必备) 推荐:★★★★★
地址:前端面试题库
for in
MDN描述:
在没有深度了解过for in的作用前,我只停留在它能够遍历对象键和数组下标的的层面上,那现在就来探究什么是一个对象中的Symbol类型键(ES6之后新加的)和可枚举属性,
JS中的对象类型包括Object,Array,Function,Date,Math....
本文主要用Object和Array来进行实践
首先,先来看一段代码:
const arr = [1, 2, 3, 4, 5]; 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
MDN描述:
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遍历值为键值对组成的数组
前端面试题库 (面试必备) 推荐:★★★★★
地址:前端面试题库