for...in... 和 for...of... 的区别

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,
  [[123]]: 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 函数返回一个对象,有 valuedone 两个属性,因此我们可以在 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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值