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

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)。

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

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

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

测试代码和截图如下

js 复制代码
  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();
相关推荐
用户新3 小时前
V8引擎 精品漫游指南--Ignition篇(下 一) 动态执行前的事情
前端·javascript
@PHARAOH5 小时前
WHAT - GitLens vs Fork
前端
yqcoder5 小时前
前端性能优化:如何减少重绘与重排?
前端·性能优化
洋子6 小时前
Yank Note 系列 13 - 让 AI Agent 进入笔记工作流
前端·人工智能
wenzhangli78 小时前
Ooder A2UI 核心架构深度解析:WEB 拦截层的设计与实现
前端·架构
前端百草阁8 小时前
【前端性能优化全链路指南】从开发编写到构建运行的多维度实践
前端·性能优化
女生也可以敲代码9 小时前
AI时代下的50道前端开发面试题:从基础到大模型应用
前端·面试
ZhengEnCi9 小时前
M5-markconv自定义CSS样式指南 📝
前端·css·python
IT_陈寒9 小时前
SpringBoot自动配置的坑差点让我加班到天亮
前端·人工智能·后端
xingpanvip9 小时前
星盘接口开发文档:星相日历接口指南
android·开发语言·前端·css·php·lua