一文看懂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();
相关推荐
环信20 小时前
2026年开发者选择即时通讯厂商应注意的几点
前端
卷帘依旧20 小时前
Generator 全面解析 + async/await 深度对比
前端·javascript
yqcoder21 小时前
数据劫持的双雄:深入解析 Object.defineProperty 与 Proxy
开发语言·前端·javascript
lichenyang45321 小时前
鸿蒙聊天 Demo 练习 03:接入 Next.js 后端接口,实现真机前后端联调
前端
小三金21 小时前
EXPO+RN echarts图表库,以及如何使用
前端·javascript·react.js
ZFSS21 小时前
Midjourney Shorten API 的集成与使用
java·前端·数据库·人工智能·ai·midjourney·ai编程
Pu_Nine_91 天前
IntersectionObserver 详解:封装 Vue 指令实现图片懒加载
前端·javascript·vue.js·性能优化
清灵xmf1 天前
Web 和 Native 是怎么“对话“的?JSBridge 解答
前端·webview·native·jsbridge·hybrid
jiayong231 天前
前端面试题库 - ES6+新特性篇
前端·面试·es6
前端那点事1 天前
Vue nextTick 超全解析|作用、使用场景、底层原理、Vue2/Vue3区别
前端·vue.js