Object.defineProperty
- Object.defineProperty 是 ES5 引入的 API,它的核心作用是精准控制对象单个属性的行为,比如能否修改、能否枚举、以及自定义属性的读取 (get) 和设置 (set) 行为;
- 基本语法:
- obj: 要定义属性的对象;
- prop: 要定义或修改的属性名;
- descriptor: 属性描述符 (核心),分为数据描述符和存取描述符;
Object.defineProperty(obj, prop, descriptor); - 核心用法示例:
// 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 - 关键特点:
- 粒度细但范围窄:只能针对单个属性进行监听 / 配置,若要监听多个属性需循环遍历;
- 功能有限:无法监听数组的下标操作 (如 arr[0] = 1)、数组的 push/pop 等方法,也无法监听对象新增 / 删除属性;
- 兼容性好:支持 IE8 及以上 (ES5 特性),是 Vue2 响应式的核心实现方案;
Proxy
-
Proxy 是 ES6 引入的新特性,它的核心作用是创建一个对象的 “代理”,从而拦截并自定义该对象的几乎所有操作 (属性读取、设置、删除、函数调用等);
-
基本语法:
- target: 要代理的目标对象 (可以是对象、数组、函数等);
- handler: 拦截器对象,包含各种拦截方法 (如 get、set、deleteProperty 等);
const proxyObj = new Proxy(target, handler); -
核心用法示例:
// 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 -
关键特点:
- 拦截范围广:支持 13 种拦截操作 (如 get、set、deleteProperty、apply、construct 等),能覆盖对象的几乎所有操作;
- 支持数组监听:可以直接拦截数组的下标修改、push/pop 等方法;
- 能监听新增 / 删除属性:弥补了 Object.defineProperty 的短板;
- 兼容性稍差:不支持 IE 浏览器,是 Vue3 响应式的核心实现方案;
Proxy vs Object.defineProperty 核心对比
| 特性 | Object.defineProperty | Proxy |
|---|---|---|
| 监听粒度 | 单个属性 | 整个对象 |
| 数组监听 | 不支持 (需重写方法) | 原生支持 |
| 新增 / 删除属性监听 | 不支持 | 支持 (deleteProperty/set) |
| 拦截操作数量 | 仅 get/set 等少数 | 13 种 (覆盖几乎所有对象操作) |
| 兼容性 | 好 (IE8+) | 差 (不支持 IE) |
| 使用复杂度 | 低 (单个属性配置) | 稍高 (需理解拦截器和 Reflect) |
实际应用场景
-
响应式数据 (Vue2/Vue3):
- Vue2 用 Object.defineProperty 实现响应式,需重写数组方法、遍历对象属性;
- Vue3 用 Proxy 实现响应式,代码更简洁,功能更完整;
-
数据校验 / 拦截:比如验证表单输入、限制属性值范围;
-
日志记录 / 调试:拦截对象操作并记录日志,方便调试;
-
函数增强:拦截函数调用,实现缓存、防抖等功能;
Reflect
Reflect 是什么?
-
Reflect 是 ES6 新增的一个内置对象 (不是构造函数,不能用 new 调用),它的核心作用是:
- 将 Object 上一些属于语言层面的方法 (如 Object.defineProperty) 迁移到 Reflect 上,让 Object 专注于对象本身的属性操作;
- 统一对象操作的 API 风格 (所有方法都是函数式调用),且返回值更合理 (比如 Reflect.defineProperty 返回布尔值,而 Object.defineProperty 失败会抛错);
- 与 Proxy 方法一一对应,成为 Proxy 拦截操作时的 “默认实现”;
-
简单来说:Reflect 把零散的对象操作 (如取值、赋值、调用函数、判断属性是否存在) 封装成了标准化、函数式的方法,让代码更易读、更易维护;
Reflect 的核心方法(与 Proxy 一一对应)
-
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 是唯一方式) -
基础用法示例:
// 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 配合实现 “拦截 + 默认行为”
-
Proxy 用于拦截对象的操作,而 Reflect 可以在拦截逻辑中调用默认的原生操作,这是 Reflect 最核心的用途;
-
比如实现一个 “日志型代理”,拦截对象的取值 / 赋值,并保留原生行为:
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 执行了默认行为) -
为什么不用 target[key] 而用 Reflect.get?
- Reflect.get 支持 receiver (绑定 this),能正确处理 getter 中的 this 指向;
- 统一返回值风格,与 Proxy 拦截方法的返回值要求匹配 (比如 set 拦截器需要返回布尔值);
场景 2:替代易出错的 Object 方法
-
传统的 Object.defineProperty 失败会抛出错误;
-
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:函数式编程中的对象操作
-
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:动态调用构造函数 / 函数
-
当不知道要调用的函数 / 构造函数具体是什么时,Reflect 的 apply 和 construct 比传统方式更灵活:
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
计算机网络🛜 session
上一篇