# 普通对象的遍历
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() |  ✅ | ❌ | ✅ | ✅ | ✅ |