for…in… 和 for…of… 的区别
文章目录
今天在抖音观看前端模拟面试的直播,突然面试者被问到这道题,这道题其实还是比较基础的,但是会引申出一些问题,在此就记录一下,比如其中有一个有趣的问题就是用 for…of… 实现遍历对象的功能。
1. for…in…
1.1 遍历对象
用 for…in… 遍历对象,可以看如下例子🌰:
let obj = {
'a': 1,
'b': 2,
'-1': 3,
'0': 4,
'1': 5,
[Symbol(1)]: 6,
[[1,2,3]]: 7,
[{}]: 8,
}
Object.defineProperty(obj, 'num', {
value: 666,
enumerable: false
})
Object.defineProperty(obj, Symbol(2), {
value: 999,
enumerable: false
})
Object.prototype.param = 123;
for (const key in obj) {
console.log(`${key}: ${obj[key]}`);
}
上面的打印结果是:
0: 4
1: 5
a: 1
b: 2
-1: 3
1,2,3: 7
[object Object]: 8
param: 123
可以看出for…in…可以遍历出对象本身的可枚举属性、原型链上的属性,但是不能遍历出Symbol属性,同时他会按照一定的顺序进行打印,首先打印key为非负数字的值,然后剩余的按添加顺序打印,数组和对象为key会转为字符串的形式。
1.2 遍历键值的方法
- for…in…
- Object.keys()
- Object.getOwnPropertySymbols()
- Object.getOwnPropertyNames()
- Reflect.ownKeys()
接下来从第二个方法开始按顺序执行,查看打印结果:
console.log(Object.keys(obj));
// [ '0', '1', 'a', 'b', '-1', '1,2,3', '[object Object]' ]
console.log(Object.getOwnPropertySymbols(obj));
// [ Symbol(1), Symbol(2) ]
console.log(Object.getOwnPropertyNames(obj));
// [ '0', '1', 'a', 'b', '-1', '1,2,3', '[object Object]', 'num' ]
console.log(Reflect.ownKeys(obj));
// ['0', '1', 'a', 'b', '-1', '1,2,3', '[object Object]', 'num', Symbol(1), Symbol(2)]
总结:
函数名 | 是否可遍历原型属性 | 是否可遍历不可枚举属性 | 是否可遍历Symbol属性 |
---|---|---|---|
for…in… | ✓ | ✗ | ✗ |
Object.keys() | ✗ | ✗ | ✗ |
Object.getOwnPropertySymbols() | ✗ | ✓ | ✓ |
Object.getOwnPropertyNames() | ✗ | ✓ | ✗ |
Reflect.ownKeys() | ✓ | ✓ | ✓ |
1.3 实现 for…of… 遍历对象
思考🤔:如果使用 for…of… 遍历对象会输出啥结果呢?
for (const key of obj) {
console.log(`${key}: ${obj[key]}`);
}
// 输出结果:TypeError: obj is not iterable
因为对象没有 iterable 接口所以不能被遍历出来,那么如果真的想用 for…in…来遍历对象呢?想要实现这个功能我们要明白为啥能用 for…of… 遍历数组等其他,这是因为原型上有 Symbol.iterator
属性,这个属性返回的是一个 iterable 接口,看如下例子🌰:
let iter = [1, 2, 3][Symbol.iterator]();
console.log(iter.next()); // { value: 1, done: false }
console.log(iter.next()); // { value: 2, done: false }
console.log(iter.next()); // { value: 3, done: false }
console.log(iter.next()); // { value: undefined, done: true }
可以看到获取的 Symbol.iterator 是一个不可枚举的函数,而且函数执行后返回的是一个对象,对象中有 next 函数,next 函数返回一个对象,有 value
和 done
两个属性,因此我们可以在 Object 的原型上可以添加自定义方法,因此改写如下:
Object.defineProperty(Object.prototype, Symbol.iterator, {
value: function () {
const keys = Object.getOwnPropertyNames(this); // 这里的this指向调用接口的对象
const that = this, len = keys.length;
let currentIndex = 0;
return {
next() {
return {
value: that[keys[currentIndex]],
done: currentIndex++ >= len
};
}
}
},
enumerable: false,
writable: false,
configurable: true,
})
// 测试
for (const item of obj) {
console.log(item);
}
// 4 5 1 2 3 7 8
2. for…of…
2.1 遍历数组
let a = [1, 2, 3];
for (const item of a) {
console.log(item);
}
// 1 2 3
2.2 遍历字符串
let a = 'hello world';
for (const item of a) {
console.log(item);
}
// h e l l o w o r l d
2.3 遍历 Set
let a = new Set([1, 2, 3]);
for (const item of a) {
console.log(item);
}
// 1 2 3
2.4 遍历 Map
let a = new Map([['key1', 0], ['key2', 1], ['key3', 2]]);
for (const item of a) {
console.log(item);
}
// [ 'key1', 0 ] [ 'key2', 1 ] [ 'key3', 2 ]
2.5 自定义 generator 函数
function* getVersion(version) {
const list = version.split(/[.-]/), len = list.length;
for (let i = 0; i < len; i++) {
yield list[i];
}
}
const iter = getVersion('1.2.3-beta');
for (const item of iter) {
console.log(item);
}
// 1 2 3 beta