Object.defineProperty

  1. Object.definePropertyES5 引入的 API,它的核心作用是精准控制对象单个属性的行为,比如能否修改、能否枚举、以及自定义属性的读取 (get) 和设置 (set) 行为;
  2. 基本语法:
    1. obj: 要定义属性的对象;
    2. prop: 要定义或修改的属性名;
    3. descriptor: 属性描述符 (核心),分为数据描述符和存取描述符;
    Object.defineProperty(obj, prop, descriptor);
    
  3. 核心用法示例:
    // 1. 基础用法:定义一个不可修改的属性
    const person = {};
    Object.defineProperty(person, 'name', {
      value: '张三',       // 属性值
      writable: false,     // 不可修改(默认 false)
      enumerable: true,    // 可枚举(默认 false)
      configurable: false  // 不可删除/重新配置(默认 false)
    });
    
    console.log(person.name); // 输出:张三
    person.name = '李四';     // 严格模式下会报错,非严格模式无效果
    console.log(person.name); // 输出:张三
    
    // 2. 存取描述符(核心):自定义 get/set
    const user = {
      _age: 18 // 下划线约定为私有属性
    };
    
    Object.defineProperty(user, 'age', {
      get() {
        console.log('读取 age 属性');
        return this._age;
      },
      set(newValue) {
        console.log('设置 age 属性为', newValue);
        if (newValue < 0) {
          this._age = 0;
        } else {
          this._age = newValue;
        }
      },
      enumerable: true
    });
    
    // 测试
    console.log(user.age); // 输出:读取 age 属性 → 18
    user.age = -5;         // 输出:设置 age 属性为 -5
    console.log(user.age); // 输出:读取 age 属性 → 0
    
  4. 关键特点:
    1. 粒度细但范围窄:只能针对单个属性进行监听 / 配置,若要监听多个属性需循环遍历;
    2. 功能有限:无法监听数组的下标操作 (如 arr[0] = 1)、数组的 push/pop 等方法,也无法监听对象新增 / 删除属性;
    3. 兼容性好:支持 IE8 及以上 (ES5 特性),是 Vue2 响应式的核心实现方案;

Proxy

  1. ProxyES6 引入的新特性,它的核心作用是创建一个对象的 “代理”,从而拦截并自定义该对象的几乎所有操作 (属性读取、设置、删除、函数调用等)

  2. 基本语法:

    1. target: 要代理的目标对象 (可以是对象、数组、函数等)
    2. handler: 拦截器对象,包含各种拦截方法 (如 get、set、deleteProperty 等)
    const proxyObj = new Proxy(target, handler);
    
  3. 核心用法示例:

    // 1. 基础用法:拦截对象的属性读取和设置
    const target = { name: '张三', age: 18 };
    const proxy = new Proxy(target, {
      // 拦截属性读取
      get(target, prop, receiver) {
        console.log(`读取 ${prop} 属性`);
        // Reflect 是 ES6 内置对象,用于规范化操作对象的行为
        return Reflect.get(target, prop, receiver);
      },
      // 拦截属性设置
      set(target, prop, value, receiver) {
        console.log(`设置 ${prop} 属性为 ${value}`);
        // 验证逻辑
        if (prop === 'age' && value < 0) {
          throw new Error('年龄不能为负数');
        }
        return Reflect.set(target, prop, value, receiver);
      },
      // 拦截属性删除
      deleteProperty(target, prop) {
        console.log(`删除 ${prop} 属性`);
        return Reflect.deleteProperty(target, prop);
      }
    });
    
    // 测试
    console.log(proxy.name); // 输出:读取 name 属性 → 张三
    proxy.age = 20;          // 输出:设置 age 属性为 20
    delete proxy.name;       // 输出:删除 name 属性
    
    // 2. 监听数组操作(Proxy 独有优势)
    const arr = [1, 2, 3];
    const arrProxy = new Proxy(arr, {
      set(target, prop, value) {
        console.log(`数组 ${prop} 位置设置为 ${value}`);
        return Reflect.set(target, prop, value);
      }
    });
    
    arrProxy.push(4); // 输出:数组 3 位置设置为 4 → 数组 length 设置为 4
    arrProxy[0] = 10; // 输出:数组 0 位置设置为 10
    
  4. 关键特点:

    1. 拦截范围广:支持 13 种拦截操作 (如 get、set、deleteProperty、apply、construct 等),能覆盖对象的几乎所有操作;
    2. 支持数组监听:可以直接拦截数组的下标修改、push/pop 等方法;
    3. 能监听新增 / 删除属性:弥补了 Object.defineProperty 的短板;
    4. 兼容性稍差:不支持 IE 浏览器,是 Vue3 响应式的核心实现方案;

Proxy vs Object.defineProperty 核心对比

特性 Object.defineProperty Proxy
监听粒度 单个属性 整个对象
数组监听 不支持 (需重写方法) 原生支持
新增 / 删除属性监听 不支持 支持 (deleteProperty/set)
拦截操作数量 get/set 等少数 13(覆盖几乎所有对象操作)
兼容性 (IE8+) (不支持 IE)
使用复杂度 (单个属性配置) 稍高 (需理解拦截器和 Reflect)

实际应用场景

  1. 响应式数据 (Vue2/Vue3)

    1. Vue2Object.defineProperty 实现响应式,需重写数组方法、遍历对象属性;
    2. Vue3Proxy 实现响应式,代码更简洁,功能更完整;
  2. 数据校验 / 拦截:比如验证表单输入、限制属性值范围;

  3. 日志记录 / 调试:拦截对象操作并记录日志,方便调试;

  4. 函数增强:拦截函数调用,实现缓存、防抖等功能;

Reflect

Reflect 是什么?

  1. ReflectES6 新增的一个内置对象 (不是构造函数,不能用 new 调用),它的核心作用是:

    1. Object 上一些属于语言层面的方法 (如 Object.defineProperty) 迁移到 Reflect 上,让 Object 专注于对象本身的属性操作;
    2. 统一对象操作的 API 风格 (所有方法都是函数式调用),且返回值更合理 (比如 Reflect.defineProperty 返回布尔值,而 Object.defineProperty 失败会抛错)
    3. Proxy 方法一一对应,成为 Proxy 拦截操作时的 “默认实现”
  2. 简单来说:Reflect 把零散的对象操作 (如取值、赋值、调用函数、判断属性是否存在) 封装成了标准化、函数式的方法,让代码更易读、更易维护;

Reflect 的核心方法(与 Proxy 一一对应)

  1. Reflect 提供了 13 个静态方法,覆盖了所有常见的对象操作,以下是最常用的方法及对比:

    Reflect 方法 等效的传统操作 核心区别
    Reflect.get(target, key) target[key] 支持第三个参数 receiver (绑定 this),属性是 getter 时生效
    Reflect.set(target, key, val) target[key] = val 返回布尔值 (表示是否设置成功),支持第四个参数 receiver
    Reflect.has(target, key) key in target 函数式调用,更易组合 (比如作为回调)
    Reflect.deleteProperty(target, key) delete target[key] 返回布尔值 (表示是否删除成功),而非返回删除后的对象
    Reflect.defineProperty(target, key, desc) Object.defineProperty 失败时返回 false,而非抛出错误
    Reflect.construct(target, args) new target(…args) 不依赖 new 关键字,可模拟构造函数调用,更灵活
    Reflect.apply(func, thisArg, args) func.apply(thisArg, args) 统一了函数调用的 API (ES6 前 Function.prototype.apply 是唯一方式)
  2. 基础用法示例:

    // 1. 取值/赋值(支持 receiver)
    const obj = {
      name: "张三",
      get age() {
        return this._age || 18;
      }
    };
    
    // 普通取值
    console.log(Reflect.get(obj, "name")); // 张三
    // 绑定 this 的取值(receiver 会替换 getter 中的 this)
    console.log(Reflect.get(obj, "age", { _age: 20 })); // 20
    
    // 2. 设置值(返回布尔值)
    console.log(Reflect.set(obj, "name", "李四")); // true
    console.log(obj.name); // 李四
    
    // 3. 判断属性是否存在
    console.log(Reflect.has(obj, "name")); // true
    console.log(Reflect.has(obj, "gender")); // false
    
    // 4. 删除属性(返回布尔值)
    console.log(Reflect.deleteProperty(obj, "name")); // true
    console.log(obj.name); // undefined
    
    // 5. 模拟 new 调用构造函数
    function Person(name) {
      this.name = name;
    }
    const p = Reflect.construct(Person, ["王五"]);
    console.log(p instanceof Person); // true
    console.log(p.name); // 王五
    
    // 6. 调用函数(替代 apply)
    function sum(a, b) {
      return a + b;
    }
    console.log(Reflect.apply(sum, null, [1, 2])); // 3
    

Reflect 的核心使用场景

场景 1:与 Proxy 配合实现 “拦截 + 默认行为”

  1. Proxy 用于拦截对象的操作,而 Reflect 可以在拦截逻辑中调用默认的原生操作,这是 Reflect 最核心的用途;

  2. 比如实现一个 “日志型代理”,拦截对象的取值 / 赋值,并保留原生行为:

    const user = {
      name: "张三",
      age: 18
    };
    
    // 代理对象:拦截取值和赋值,并打印日志
    const proxyUser = new Proxy(user, {
      // 拦截取值
      get(target, key, receiver) {
        console.log(`读取属性 ${key}:${target[key]}`);
        // 调用 Reflect.get 执行原生的取值逻辑(等价于 return target[key],但更标准)
        return Reflect.get(target, key, receiver);
      },
      // 拦截赋值
      set(target, key, value, receiver) {
        console.log(`设置属性 ${key}:${value}`);
        // 调用 Reflect.set 执行原生的赋值逻辑,返回布尔值
        return Reflect.set(target, key, value, receiver);
      }
    });
    
    // 使用代理对象
    proxyUser.name; // 打印:读取属性 name:张三
    proxyUser.age = 20; // 打印:设置属性 age:20
    console.log(user.age); // 20(原生对象也被修改,因为 Reflect 执行了默认行为)
    
  3. 为什么不用 target[key] 而用 Reflect.get

    1. Reflect.get 支持 receiver (绑定 this),能正确处理 getter 中的 this 指向;
    2. 统一返回值风格,与 Proxy 拦截方法的返回值要求匹配 (比如 set 拦截器需要返回布尔值)

场景 2:替代易出错的 Object 方法

  1. 传统的 Object.defineProperty 失败会抛出错误;

  2. Reflect.defineProperty 返回布尔值,更易处理异常;

    const obj = {};
    
    // 传统方式:需要 try/catch 捕获错误
    try {
      Object.defineProperty(obj, "name", { value: "张三", writable: false });
      Object.defineProperty(obj, "name", { value: "李四" }); // 不可写,抛错
    } catch (e) {
      console.log("定义属性失败:", e.message);
    }
    
    // Reflect 方式:无需 try/catch,直接判断返回值
    if (!Reflect.defineProperty(obj, "name", { value: "李四" })) {
      console.log("定义属性失败:属性不可写"); // 执行这行
    }
    

场景 3:函数式编程中的对象操作

  1. Reflect 的方法都是纯函数 (输入确定则输出确定),适合在函数式编程中作为回调使用:

    const arr = [
      { name: "张三", age: 18 },
      { name: "李四", age: 20 }
    ];
    
    // 批量读取所有对象的 name 属性(函数式风格)
    const names = arr.map(item => Reflect.get(item, "name"));
    console.log(names); // ["张三", "李四"]
    
    // 批量判断是否有 gender 属性
    const hasGender = arr.map(item => Reflect.has(item, "gender"));
    console.log(hasGender); // [false, false]
    

场景 4:动态调用构造函数 / 函数

  1. 当不知道要调用的函数 / 构造函数具体是什么时,Reflectapplyconstruct 比传统方式更灵活:

    function createInstance(Constructor, ...args) {
      return Reflect.construct(Constructor, args);
    }
    
    // 调用 Person 构造函数
    const p1 = createInstance(Person, "张三");
    // 调用 Array 构造函数
    const arr = createInstance(Array, 1, 2, 3);
    console.log(arr); // [1, 2, 3]
    
    function callFunction(func, thisArg, ...args) {
      return Reflect.apply(func, thisArg, args);
    }
    
    console.log(callFunction(sum, null, 10, 20)); // 30
    console.log(callFunction(Math.max, null, 1, 5, 3)); // 5
    
打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

你好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. Object.defineProperty
  2. 2. Proxy
    1. 2.1. Proxy vs Object.defineProperty 核心对比
    2. 2.2. 实际应用场景
  3. 3. Reflect
    1. 3.1. Reflect 是什么?
    2. 3.2. Reflect 的核心方法(与 Proxy 一一对应)
    3. 3.3. Reflect 的核心使用场景
      1. 3.3.1. 场景 1:与 Proxy 配合实现 “拦截 + 默认行为”
      2. 3.3.2. 场景 2:替代易出错的 Object 方法
      3. 3.3.3. 场景 3:函数式编程中的对象操作
      4. 3.3.4. 场景 4:动态调用构造函数 / 函数