# 普通对象的遍历

const privateProp = Symbol('private')
const noEnumerPrivateProp = Symbol('noEnumerPrivate')
const privateProtoProp = Symbol('privateProto')
const noEnumerPrivateProtoProp = Symbol('noEnumerPrivateProto')

const sup = Object.defineProperties(
  {},
  {
    title: {
      value: '原型上的可枚举属性',
      enumerable: true,
    },
    description: {
      value: '原型上的不可枚举属性',
      enumerable: false,
    },
    [privateProtoProp]: {
      value: '原型上的可枚举私有原型属性',
      enumerable: true,
    },
    [noEnumerPrivateProtoProp]: {
      value: '原型上的不可枚举私有原型属性',
      enumerable: false,
    },
  },
)

const sub = Object.create(sup, {
  name: {
    value: '实例上的可枚举属性',
    enumerable: true,
  },
  age: {
    value: '实例上的不可枚举属性',
    enumerable: false,
  },
  [privateProp]: {
    value: '实例上的可枚举私有属性',
    enumerable: true,
  },
  [noEnumerPrivateProp]: {
    value: '实例上的不可枚举私有属性',
    enumerable: false,
  },
})

console.log(sub)
// {name: "实例上的可枚举属性", age: "实例上的不可枚举属性", Symbol(private): "实例上的可枚举私有属性", Symbol(noEnumerPrivate): "实例上的不可枚举私有属性"}

# in

判断属性是否存在。

如果实例对象的属性存在、或则继承自对象的原型,那么无论是否可枚举、是否是 Symbol,in 运算符都会返回 true

// 自身可枚举
console.log('name' in sub) // true
// 自身不可枚举
console.log('age' in sub) // true
// 原型可枚举
console.log('title' in sub) // true
// 原型不可枚举
console.log('description' in sub) // true

// 自身 Symbol 可枚举
console.log(privateProp in sub) // true
// 自身 Symbol 不可枚举
console.log(noEnumerPrivateProp in sub) // true
// 原型 Symbol 可枚举
console.log(privateProtoProp in sub) // true
// 原型 Symbol 不可枚举
console.log(noEnumerPrivateProtoProp in sub) // true

# for...in

以任意顺序遍历一个对象的除 Symbol 以外的可枚举属性,包括该对象从其构造函数原型中继承的属性。

数组索引只是具有整数名称的枚举属性,并且与通用对象属性相同。不能保证 for...in 将以任何特定的顺序返回索引。

在迭代过程中最好不要在对象上进行添加、修改或者删除属性的操作,除非是对当前正在被访问的属性。

如果你只要考虑对象本身的属性,而不是它的原型,那么使用 getOwnPropertyNames() 或执行 hasOwnProperty() 来确定某属性是否是对象本身的属性。

for (const prop in sub) {
  console.log(prop, sub[prop])
}

// name 实例上的可枚举属性
// title 原型上的可枚举属性

# for...of

在可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句。

默认情况下,普通对象是不可并迭代的,所以直接遍历会报错。

for (const prop of sub) {
  console.log(prop, sub[prop])
}
// Uncaught TypeError: sub is not iterable

如果你想要遍历普通对象可以先为其指定 Symbol.iterator 方法。

sub[Symbol.iterator] = function() {
  return {
    i: 0,
    _keys: Object.keys(this),
    next() {
      if (this.i < this._keys.length) {
        return { value: this._keys[this.i++], done: false }
      }
      return { value: undefined, done: true }
    },
  }
}

# Object.keys()

返回给定对象自身的所有可枚举属性的字符串数组。

数组中属性名的排列顺序和使用 for...in 循环遍历该对象时返回的顺序一致。如果对象的键-值都不可枚举,那么将返回由键组成的数组。

console.log(Object.keys(sub)) // ["title"]

# Object.getOwnPropertyNames()

返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括 Symbol 值作为名称的属性)组成的数组。

数组中枚举属性的顺序与通过 for...in 循环(或 Object.keys)迭代该对象属性时一致。数组中不可枚举属性的顺序未定义。

console.log(Object.getOwnPropertyNames(sub)) // ["title", "age"]

# Object.getOwnPropertySymbols()

返回一个给定对象自身的所有 Symbol 属性的数组。

console.log(Object.getOwnPropertySymbols(sub)) // [Symbol(private), Symbol(noEnumerPrivate)]

# Reflect.ownKeys()

返回一个由目标对象自身的属性键组成的数组。

返回值等同于 Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))

console.log(Reflect.ownKeys(sub)) // ["title", "other", Symbol(private), Symbol(noEnumerPrivate)]

# 总结

名称 自身 继承 可枚举 不可枚举 Symbol
in
for...in
for...of
Object.keys()
Object.getOwnPropertyNames()
Object.getOwnPropertySymbols() ✅(Symbol)
Reflect.ownKeys()