一文看懂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();
相关推荐
橙子家4 小时前
浏览器缓存之【基础键值存储】:Local storage 和 Session storage
前端
星星在线6 小时前
MusicFree:一个「All in One」的个人音乐服务器,让听歌回归简单
前端·后端
IT_陈寒7 小时前
Redis的SETNX并发问题让我加了三天班
前端·人工智能·后端
demo007x7 小时前
Docling 文档转换以及技术架构分析
前端·后端·程序员
京东云开发者8 小时前
京东市民服务又“上新”!这次是黑龙江“龙易办”
前端
袋鱼不重9 小时前
我的神奇同事,AI 用多了居然写了个 Open In Codex
前端·后端·ai编程
Fireworks9 小时前
深入vue3源码解读 -- 1、响应式的基础概念
前端
程序员黑豆9 小时前
JDK 下载安装与配置详细教程
java·前端·ai编程
hunterandroid9 小时前
文件存储:内部存储与外部存储
前端
NorBugs10 小时前
飞机大战 Low 版 (Made in AI)
前端