JavaScript 对象的属性枚举与迭代

JavaScript 的对象具有多种属性和方法,这些属性和方法根据其定义和来源具有不同的可枚举性。在这篇博客中,我们将深入探讨对象中的属性枚举、迭代,以及如何处理和修改它们。

前提了解

为了彻底搞清楚 JavaScript 中的对象属性的枚举与迭代,我们先从对象属性的特性开始,然后讨论迭代方式。

对象属性的特性

每个对象属性都有以下三个相关的特性:

  • enumerable:如果为 true,属性可以被 for...in 循环枚举,并且可以被 Object.keys() 返回。
  • configurable:如果为 true,该属性的描述符可以被改变,且属性可以被删除。
  • writable:如果为 true,属性值可以被赋值运算符改变。
    这些特性可以通过 Object.defineProperty() 设置。

例如:

let obj = {};
Object.defineProperty(obj, 'nonEnumerableProp', {
  enumerable: false,
  configurable: true,
  writable: true,
  value: 'You cannot see me in for...in loop'
});

1. 对象属性的来源

JavaScript 对象的属性主要来源于两个地方:

  • 直接定义在对象上的属性(自身属性)。
  • 从原型链上继承的属性。

2. 属性的可枚举性

属性的可枚举性决定了属性是否可以在某些操作(如 for...in 循环)中被列举出来。这个特性可以通过属性描述符来设置或查看。

枚举≠迭代!!!

3. 不同的属性遍历方式

for...in 遍历可枚举属性:此方法遍历对象自身以及原型链中的所有可枚举属性。(除非属性名是Symbol)

for (let prop in obj) {
  console.log(prop);
}

Object.keys():返回一个由对象自身的可枚举属性组成的数组。

Object.getOwnPropertyNames():返回一个由对象自身的所有属性(包括不可枚举属性)组成的数组。

Object.values() 和 Object.entries():这两个方法也只考虑对象自身的可枚举属性。

for...of 遍历迭代器:直接用于对象会报错,因为对象本身不是迭代器。但与 for...of 相关的是,你可以用它迭代任何具有 [Symbol.iterator] 属性的对象,如数组、字符串、Set 和 Map。

for...offor...in 的关系:

for...in:遍历对象的 可枚举属性。
const obj = { a: 1, b: 2 };
for (let key in obj) {
    console.log(key);  // 输出 "a" 和 "b"
}
for...of:用于迭代 迭代对象,如数组、字符串、Set 和 Map。
const arr = [1, 2, 3];
for (let value of arr) {
    console.log(value);  // 输出 1、2 和 3
}

注意:要在对象上使用 for...of,你需要为该对象定义一个 [Symbol.iterator] 函数或使用可以迭代的内置对象如 Map 或 Set。

4. 获取自身的不可枚举的属性名

如果你想从一个对象中仅获取那些不可枚举的属性名,可以使用以下方法:

const target = myObject;
const enumAndNonenum = Object.getOwnPropertyNames(target);
const enumOnly = new Set(Object.keys(target));
const nonenumOnly = enumAndNonenum.filter((key) => !enumOnly.has(key));

console.log(nonenumOnly);

在上面的代码中,我们首先使用 Object.getOwnPropertyNames() 获取对象的所有自身属性(包括不可枚举的属性)。然后使用 Object.keys() 获取所有的可枚举属性,并将它们存储在一个集合中。最后,我们筛选出那些不在可枚举属性集合中的属性,这些属性就是不可枚举的属性。

5. 如何使 JavaScript 对象的属性可迭代

迭代和枚举是两个不同的概念。迭代意味着你可以使用for...of循环来遍历一个对象的元素。而在JavaScript中,对象自身并不是迭代的。为了使对象的属性可以迭代,我们需要使用 Symbol.iterator。

要使JS对象的属性可以迭代,我们需要在对象上定义一个 [Symbol.iterator] 方法。此方法会返回一个对象,该对象必须有一个名为 next 的方法,该方法每次被调用时都会返回一个具有 valuedone 属性的对象。

以下是一个示例:

const obj = {
  a: 'one',
  b: 'two',
  c: 'three'
};

obj[Symbol.iterator] = function() {
  const keys = Object.keys(this);
  let index = 0;

  return {
    next: () => {
      if (index < keys.length) {
        return {
          value: this[keys[index++]],
          done: false
        };
      } else {
        return { done: true };
      }
    }
  };
};

for (const value of obj) {
  console.log(value);
}

在上面的例子中,当我们尝试用 for...of 循环遍历对象时,它会使用我们为对象定义的 [Symbol.iterator] 函数,从而使我们可以按照我们选择的顺序迭代对象的属性。

6. 如何判断对象自身或包含原型链上是否含有某个属性

判断对象自身是否含有某个属性

xxx.hasOwnProperty来判断,若有则返回true

function Obj() {}
Obj.prototype.a = 1;

const obj = {};

console.log(obj.hasOwnProperty('a')); // => false

判断对象(包含原型链上)是否含有某个属性

'xxx' in obj来判断,若有则返回true。(不论是否可枚举)

const obj = {
    a: undefined
};

console.log('a' in obj); // => true

注意和obj.a !== undefined进行区分

function Obj() {}
Obj.prototype.a = 1;

const obj = {
    a: undefined
};

console.log(obj.hasOwnProperty('a')); // => true
console.log('a' in obj); // => true
console.log(obj.a !== undefined); // => false

总结

理解 JavaScript 对象属性的可枚举性和如何迭代这些属性是任何开发者的基础工具箱的重要组成部分。希望这篇博客帮助你更深入地理解这些概念,并为你的项目提供了实用的工具和技巧。

补充:用一段代码弄清楚对象属性相关的内容

let d = '888';
let g = '999';
let h = 'hhh';
function Obj() {
    let a = '1';
    let b = () => '2';
    function c() {
        return '3';
    }
    // 以上变量在Obj()或new Obj()的过程中会被创建,但new Obj()返回的对象的属性中不含这些变量。
    console.log('\nexec:', a, b, c);

    this.d = '4';
    this.e = () => '5';
    this.g = '111'; // Obj()或new Obj()后函数外部的变量g都不会被改变,new出来的对象中的g为'111'
    h = 'hed'; // Obj()或new Obj()后函数外部的变量h都会被改变,new出来的对象中不含h

    // 以上变量在Obj()或new Obj()的过程中会被创建,在new Obj()返回的对象的属性中也含这些变量。
    console.log('\nthis:', this.d, this.e);
}

let obj = new Obj();
Obj();

console.log('\nd:', d); // => d: 888
console.log('\ng:', g, '\n'); // => g: 999
console.log('\nh:', h, '\n'); // => h: hed

Object.defineProperty(obj, 'f', {
    value: '6',
    writable: false,
    enumerable: false,
    configurable: false,
});

console.log('自身可枚举属性:', Object.keys(obj));
console.log('自身全部属性:', Object.getOwnPropertyNames(obj));
console.log('自身全部属性的描述:', Object.getOwnPropertyDescriptors(obj));

终端输出

exec: 1 [Function: b] [Function: c]

this: 4 [Function (anonymous)]

exec: 1 [Function: b] [Function: c]

this: 4 [Function (anonymous)]

d: 888

g: 999 

h: hed 

自身可枚举属性: [ 'd', 'e', 'g' ]
自身全部属性: [ 'd', 'e', 'g', 'f' ]
自身全部属性的描述: {
  d: { value: '4', writable: true, enumerable: true, configurable: true },
  e: {
    value: [Function (anonymous)],
    writable: true,
    enumerable: true,
    configurable: true
  },
  g: {
    value: '111',
    writable: true,
    enumerable: true,
    configurable: true
  },
  f: {
    value: '6',
    writable: false,
    enumerable: false,
    configurable: false
  }
}

A Student on the way to full stack of Web3.