一文看懂js中所有属性访问api

111 阅读4分钟

js对象属性的遍历有很多方法,比如for inObject.keysReflect.ownKeys等等,用起来不知道选哪个,记起来麻烦,本文将介绍所有属性访问相关api的能力和区别。

前置知识:对象属性的「3 种分类」

所有遍历方法的核心差异,本质都是「能遍历哪些类型的属性」,必须先明确对象属性的 3 类划分:

1. 可枚举属性 vs 不可枚举属性

属性的 enumerable 特性为 true 是「可枚举属性」,false 是「不可枚举属性」;

  • 默认:我们直接给对象定义的属性(obj.name = 'xxx')、字面量声明的属性,默认可枚举
  • 不可枚举:JS 内置属性(比如 obj.toStringobj.__proto__)、通过 Object.defineProperty 手动设置 enumerable: false 的属性,都是不可枚举属性

2. 自有属性(自有属性) vs 继承属性

  • 自有属性:对象自己本身拥有的属性(挂载在对象自身,不在 __proto__ 原型链上);
  • 继承属性:从原型链上继承过来的属性(比如 obj.hasOwnPropertyobj.toString 都是从 Object.prototype 继承的)。

3. 字符串属性 vs Symbol 属性

JS 对象的属性名,可以是「字符串」或「Symbol 类型」(ES6 新增);

  • 字符串属性:最常用的 obj.nameobj['age'] 都属于此类;
  • Symbol 属性:const s = Symbol('id'); obj[s] = 100,这种属性名是唯一的,默认不会被常规遍历方法捕获。

不同属性访问(遍历/判断)方法区别

方法访问范围能否访问 Symbol能否访问不可枚举能否访问继承属性返回值 / 形式
JSON.stringify可枚举字符串属性JSON字符串
for...in可枚举字符串属性循环,直接拿 key
Object.keys自有可枚举字符串属性字符串数组
Object.values自有可枚举字符串属性值的数组
Object.entries自有可枚举字符串属性二维键值对数组
Object.getOwnPropertyNames自有所有字符串属性字符串数组
Object.getOwnPropertySymbols自有所有 Symbol 属性Symbol 数组
Reflect.ownKeys自有所有属性混合类型数组
Object.getOwnPropertyDescriptors自有所有属性属性描述符对象
hasOwnProperty自有所有属性布尔
Object.hasOwn自有所有属性布尔
in所有属性布尔
Reflect.has所有属性布尔

规律小结

  • 最局限的5个方法刚好也是最常用的 JSON.stringify,for...in,Object.keys ,Object.values ,Object.entries ,既无法访问symbol属性,也无法访问不可枚举和继承属性。
  • own的api,都无法访问继承属性
  • in操作符和Reflect.has 可以访问到所有属性

关于继承的说明

本文中的继承,是指原型链访问,并非面向对象中的类继承(extends)。

class User  {
    name = "小王"; //自有属性
    work() {}
}
const p = new User();

这里的p对象的name属性,是它的自有属性,而work方法继承自原型链,因此own类方法无法访问到work。

image.png

es6中的继承,沿用了es5中的属性私有,方法公开的继承最佳实践,因此es6中extends自父类的成员属性,也是该实例自有属性。

测试代码和截图如下

  class Persion {
        alive = true; //继承属性
        study() {}
      }
      class User extends Persion {
        name = "小王"; //自有属性
        work() {}
      }
      const p = new User();
      //wealth 不可枚举
      Object.defineProperty(p, "wealth", {
        enumerable: false,
        writable: true,
        value: 1000000,
      });
      p.age = 18; //age自有属性
      const CAREER = Symbol("career");
      const ID = Symbol("id");
      p[ID] = "9523"; //ID symbol属性
      Object.defineProperty(p, CAREER, {
        enumerable: false,
        writable: true,
        value: "detective",
      });
      console.group("for in");
      for (const key in p) {
        console.log(key); // alive name age
      }
      console.groupEnd("for in");
      console.log(Object.keys(p)); // ['alive', 'name', 'age']
      console.log(Object.values(p)); //[true, '小王', 18]
      console.log(Object.entries(p)); // '[["alive",true],["name","小王"],["age",18]]'
      // -----------------------可得到非symbol自有属性,无法获取不可枚举属性
      console.log("Object.getOwnPropertyNames", Object.getOwnPropertyNames(p)); // ['alive', 'name', 'wealth', 'age']
      //------------------------可得到symbol属性,包括不可枚举的symbol
      console.log(
        "Object.getOwnPropertySymbols",
        Object.getOwnPropertySymbols(p)
      ); //[Symbol(id),Symbol(career)]
      //------------------------可得到所有自有属性,包括不可枚举和symbol属性,无法获取继承属性
      console.log("Reflect.ownKeys", Reflect.ownKeys(p)); // ['alive', 'name', 'wealth', 'age', Symbol(id),Symbol(career)]
      //------------------------可得到所有自有属性,包括不可枚举和symbol属性,无法获取继承属性
      console.log(
        "Object.getOwnPropertyDescriptors",
        Object.getOwnPropertyDescriptors(p)
      );

      //继承属性返回false
      console.group("p.hasOwnProperty");
      ["alive", "study", "work", "name", "age", "wealth", ID, CAREER].forEach(
        (name) => {
          console.log(
            `p.hasOwnProperty(${name.toString()})`,
            p.hasOwnProperty(name)
          );
        }
      );
      console.groupEnd();
      //继承属性返回false
      console.group("Object.hasOwn");
      ["alive", "study", "work", "name", "age", "wealth", ID, CAREER].forEach(
        (name) => {
          console.log(
            `Object.hasOwn(p, ${name.toString()})`,
            Object.hasOwn(p, name)
          );
        }
      );
      console.groupEnd();
      // 可判断所有属性,包括不可枚举/symbol/继承
      console.group("in");
      ["alive", "study", "work", "name", "age", "wealth", ID, CAREER].forEach(
        (name) => {
          console.log(`${name.toString()} in p`, name in p);
        }
      );
      console.groupEnd();
      // 可判断所有属性,包括不可枚举/symbol/继承
      console.group("Reflect.has");
      ["alive", "study", "work", "name", "age", "wealth", ID, CAREER].forEach(
        (name) => {
          console.log(
            `Reflect.has(p, ${name.toString()})`,
            Reflect.has(p, name)
          );
        }
      );
      console.groupEnd();

image.png