关于js中对象的枚举和数组的遍历

514 阅读4分钟

对象

关于对象的枚举和遍历,我们通常有几种方式去进行操作:

  1. 'xxx' in myObject in操作符会检查该属性是否在该对象上或者其[[Prototype]]原型链上,无论该属性是否可枚举。
    let myObject = {
        a: 'b'
    }
    console.log('a' in myObject) // true
    console.log('b' in myObject) // false
    
    function createAKey(params) {
      this.a = 'a'
    }
    let aInstance = new createAKey()
    createAKey.prototype.a = 'a'
    aInstance.b = 'b'
    // 通过构造函数原型链找到的 a 这个属性值
    console.log('a' in aInstance); // true
    console.log('b' in aInstance); // true
  1. hasOwnProperty Obejct.hasOwnProperty()只会检查该属性是否在该对象上,而不会沿着[[Proptotype]]链继续往下查找
    let myObject = {
        a: 'b'
    }
    myObject.hasOwnProperty('a') // true
    myObject.hasOwnProperty('b') // false
    
    function createAKey(params) {
      this.a = 'a'
    }
    let aInstance = new createAKey()
    createAKey.prototype.a = 'a'
    aInstance.b = 'b'
    // aInstance这个实例对象上没有a这个属性 所以返回的是false
    aInstance.hasOwnProperty('a') // false
    aInstance.hasOwnProperty('b') // true
  1. getOwnPropertyNames Object.getOwnPropertyNames会返回一个数组,数组包含了当前对象所有直接包含的属性(不包括其[[Prototype]]原型链上的属性),无论该属性是否可枚举。
    function createAKey(params) {
    }
    createAKey.prototype.a = 'a'
    let aInstance = new createAKey()
    aInstance.b = 'b' // 默认为可枚举
    Object.defineProperty(aInstance, 'c', {
      value: 'c',
      enumerable: true // 设置为可枚举
    })
    Object.defineProperty(aInstance, 'd', {
      value: 'd',
      enumerable: false // 设置为不可枚举
    })
    console.log(Object.getOwnPropertyNames(aInstance)) // ['b', 'c', 'd']
  1. keys Object.keys()会返回一个数组,数组包含了当前对象所有直接包含的可枚举属性(不包括其[[Prototype]]原型链上的属性)

  2. for...in for...in遍历对象,只能遍历出对象中可以访问的属性及其[[Prototype]]原型链上可以访问的属性,通过defineProperty定义的不可枚举的属性是没办法遍历出来的。

    function createAKey(params) {
    }
    createAKey.prototype.a = 'a'
    let aInstance = new createAKey()
    aInstance.b = 'b' // 默认为可枚举
    Object.defineProperty(aInstance, 'c', {
      value: 'c',
      enumerable: true // 设置为可枚举
    })
    Object.defineProperty(aInstance, 'd', {
      value: 'd',
      enumerable: false // 设置为不可枚举
    })
    for (let v in aInstance) {
      console.log(v);
    }
    // b c a

tips: for...in最好是用在对象上进行遍历,如果你在数组上用for...in进行循环遍历可能会出现不可预料的结果,因为这种遍历不仅会包含所有数组的索引,还会包含所有可枚举的属性,如果要去对数组进行遍历,那么最好用for...of来操作。

数组

对于数组的遍历我们通常用for...of、Array.forEach等一系列相关的操作方法

    let arr = [1,4,2,3]
    for (let val of arr) {
      console.log(val);
    }
    // 1 4 2 3

for...in去遍历对象只能拿到对象的属性,而无法直接拿到对象的值,但是我们用for...of去遍历数组可以直接拿到索引对应的值,这其中是因为for...of会默认调用被访问对象的迭代器对象,然后通过调用迭代器对象的next方法来遍历所有返回值。

数组有内置的@@iterator,因此for...of可以直接用在数组上,我们也可以手动的内置的@@iterator来手动遍历数组:

    let arr = [1,4,2,3]
    
    let arrIt = arr[Symbol.iterator]()
    
    console.log(arrIt.next()) // {value: 1, done: false}
    console.log(arrIt.next()) // {value: 4, done: false}
    console.log(arrIt.next()) // {value: 2, done: false}
    console.log(arrIt.next()) // {value: 3, done: false}
    console.log(arrIt.next()) // {value: undefined, done: true}

这里的@@iterator本身不是一个迭代器对象而是一个返回迭代器对象的函数,因为同一个数组生成的不同迭代器对象之间是互干扰的:

    let arr = [1,4,2,3]

    let arrItOne = arr[Symbol.iterator]()
    let arrItTwo = arr[Symbol.iterator]()
    
    console.log(arrItOne.next()) // {value: 1, done: false}
    console.log(arrItTwo.next()) // {value: 1, done: false}
    console.log(arrItOne.next()) // {value: 4, done: false}
    console.log(arrItOne.next()) // {value: 2, done: false}
    console.log(arrItTwo.next()) // {value: 4, done: false}

通过这个逻辑,我们可以给对象定义一个生成迭代器对象的方法,使得我们可以在对象上使用for...of

    let aObj = {
      a: 'aValue',
      b: 'bValue',
      [Symbol.iterator]: function () {
        let o = this, idx = 0, ks = Object.keys(o), ksLength = ks.length
        return {
          next: () => {
            return {
              value: o[ks[idx++]],
              done: (idx > ksLength)
            }
          }
        }
      }
    }

    Object.defineProperty(aObj, 'c', {
      value: 'cValue',
      enumerable: true // 设置为可枚举
    })
    
    Object.defineProperty(aObj, 'd', {
      value: 'dValue',
      enumerable: false // 设置为不可枚举
    })

    for (let v of aObj) {
      console.log(v);
    }
    // aValue bValue cValue

由于我们用的Object.keys来获取的属性,因此这里的for...of只能返回该对象自己直接包含的可枚举属性的值

小结

对于对象,in 操作符和Object.hasOwnproperty()都会返回一个boolean值判断该属性是否存在于对象上,而这两者的区别在于是否会通过[[Prototype]]去进行查找。而Obeject.getOwnPropretyNames()和Object.keys都会反一个该对象直接包含的所有属性的数组,这两者的区别在于是否属性是否可以枚举。目前js中并没有内置的方法可以让我们获取所有in操作符能访问到的属性列表,我们只能去递归遍历某个对象的整条[[Prototye]]链并通过Object.keys()保存该层的所有属性。同时我们可以通过给对象定义一个他自己的@@iterator,让这个对象可以用for...of进行遍历

对于数组,我们通过通常通过for...of和Object.propertype上的一些列方法去进行遍历,在数组上我们最好不要用for...in去进行遍历,因为for...in不仅仅会遍历出数组所有的索引值,还会遍历出该数组的所有可枚举属性。

改文章是本人看了《你不知道的JavaScript》上卷的第三章后所记录的总结,所以文章中大量引用了书中的文字片段,如果读者觉得哪些地方写的不好可以直接提出来,同时也建议读者可以看看《你不知道的JavaScript》这本书,写的真的很好!。