defineProperty 基本语法
Object.defineProperty(obj, propName, descriptor)
- obj: 要定义属性的目标对象。
- ropName: 要定义或修改的属性名。
- descriptor: 属性描述对象,定义这个属性的行为。
选项 | 作用 | 默认值 |
---|
value | 属性的值 | undefined |
writable | 是否可被修改 | false |
enumerable | 是否能通过 for...in 或 Object.keys() 枚举出来 | false |
configurable | 是否能删除或再次修改描述符 | false |
get() | 属性被访问时的回调 | - |
set(val) | 属性被赋值时的回调 | - |
常用用法示例
const user = {};Object.defineProperty(user, 'id', {value: 123,writable: false, // 不可写enumerable: true, // 可遍历configurable: false // 不可删除
});console.log(user.id); // 123user.id = 999;
console.log(user.id); // 仍然是 123,不会改变
示例 2:使用 get/set 创建计算属性(或双向绑定)
const person = {firstName: '张',lastName: '三'
};Object.defineProperty(person, 'fullName', {get() {return this.firstName + this.lastName;},set(newVal) {const [first, last] = newVal.split(' ');this.firstName = first;this.lastName = last;},enumerable: true
});console.log(person.fullName); // 张三person.fullName = '李 四';
console.log(person.firstName); // 李
console.log(person.lastName); // 四
示例 3:隐藏属性(不能被枚举)
const config = {};Object.defineProperty(config, 'secret', {value: '123456',enumerable: false // 不可枚举
});console.log(config.secret); // 123456
console.log(Object.keys(config)); // []
示例 4:防止属性被删除或重新配置
const settings = {};Object.defineProperty(settings, 'theme', {value: 'dark',configurable: false
});delete settings.theme; // 删除失败
console.log(settings.theme); // 'dark'
. 数据劫持 —— Vue 2 响应式系统的核心
let val = 'hello';
const obj = {};
Object.defineProperty(obj, 'msg', {get() {console.log('被读取了');return val;},set(newVal) {console.log('被修改了');val = newVal;}
});obj.msg; // 被读取了
obj.msg = 123; // 被修改了
构建只读属性或安全接口(封装设计)
class Point {constructor(x, y) {Object.defineProperty(this, 'x', {value: x,writable: false});Object.defineProperty(this, 'y', {value: y,writable: false});}
}const p = new Point(1, 2);
p.x = 100; // 修改失败
console.log(p.x); // 1
添加元信息(如 Vue3 中 __v_isReactive 等)
const obj = {};
Object.defineProperty(obj, '__v_isReactive', {value: true,enumerable: false
});
和 Object.defineProperties() 的区别
Object.defineProperties(obj, {prop1: { ... },prop2: { ... },...
});
和普通赋值有啥不同?
普通赋值 | defineProperty |
---|
简单易写 | 功能强大、可控制 |
不支持 getter/setter | 支持访问器属性 |
所有属性默认可写、可删、可枚举 | 需要手动设置 |
无法隐藏属性 | 可隐藏、不枚举 |
Object.defineProperty 能干什么?
功能 | 示例 |
---|
创建只读属性 | 常用于常量、ID 等 |
定义计算属性 | getter/setter |
数据劫持 / 监听 | Vue 2 响应式核心 |
隐藏属性 | 不可枚举、用于元信息 |
创建不可删除属性 | 配置对象安全性 |
构建调试信息 | React.memo 的 displayName 等 |
框架 | 响应式/状态追踪机制 |
---|
Vue 2 | Object.defineProperty |
Vue 3 | Proxy (响应式系统) |
React | 不使用 Proxy,使用状态快照 + 比较机制(setState/useState) |
什么是 Proxy
Proxy 是 ES6 引入的一个内置对象,它用于创建一个对象的代理,从而拦截并自定义基本操作(如属性读取、赋值、函数调用等)。
const proxy = new Proxy(target, handler);
- arget:要代理的目标对象
- handler:包含拦截行为的对象(称为“捕捉器”)
Proxy 基础用法
拦截属性读取 (get)
const obj = { name: '张三' };const proxy = new Proxy(obj, {get(target, key) {console.log(`读取属性:${key}`);return target[key];}
});console.log(proxy.name); // 输出:读取属性:name
拦截属性设置 (set)
const proxy = new Proxy({}, {set(target, key, value) {console.log(`设置属性:${key} = ${value}`);target[key] = value;return true;}
});proxy.age = 25; // 输出:设置属性:age = 25
拦截 in 操作符 (has)
const proxy = new Proxy({ name: 'Vue' }, {has(target, key) {return key === 'name';}
});console.log('name' in proxy); // true
console.log('age' in proxy); // false
拦截删除操作 (deleteProperty)
const obj = { foo: 'bar' };const proxy = new Proxy(obj, {deleteProperty(target, key) {console.log(`删除属性:${key}`);return delete target[key];}
});delete proxy.foo; // 输出:删除属性:foo
捕捉器 | 说明 |
---|
get | 拦截属性读取 |
set | 拦截属性设置 |
has | 拦截 in 操作 |
deleteProperty | 拦截 delete 操作 |
ownKeys | 拦截 Object.keys() 、for...in |
defineProperty | 拦截 Object.defineProperty() |
getOwnPropertyDescriptor | 拦截属性描述符读取 |
setPrototypeOf / getPrototypeOf | 拦截原型操作 |
使用场景
数据响应式(Vue 3)
- 最重要的应用就是 Vue 3 的响应式系统。
- 通过拦截对象的 get 和 set,实现自动追踪依赖和更新视图。
数据访问控制(如私有属性保护)
const user = { _secret: '123', name: 'Admin' };const secureUser = new Proxy(user, {get(target, key) {if (key.startsWith('_')) {throw new Error('禁止访问私有属性');}return target[key];}
});
监控日志、调试数据访问
const state = new Proxy({}, {get(target, key) {console.log(`读取:${key}`);return target[key];},set(target, key, val) {console.log(`修改:${key} = ${val}`);target[key] = val;return true;}
});
虚拟属性(动态计算返回值)
const data = {name: 'Vue'
};const proxy = new Proxy(data, {get(target, key) {if (key === 'upperName') {return target.name.toUpperCase();}return target[key];}
});console.log(proxy.upperName); // 输出:VUE
Vue 3 中 Proxy 的应用原理
核心方法:reactive()import { reactive } from 'vue';const state = reactive({count: 0
});
实现机制简述
function reactive(target) {return createReactiveObject(target);
}
内部会创建一个 Proxy(target, handler):
const mutableHandlers = {get(target, key, receiver) {const result = Reflect.get(target, key, receiver);track(target, key); // 依赖收集return isObject(result) ? reactive(result) : result;},set(target, key, value, receiver) {const oldValue = target[key];const result = Reflect.set(target, key, value, receiver);if (oldValue !== value) {trigger(target, key); // 通知更新}return result;}
};function createReactiveObject(target) {return new Proxy(target, mutableHandlers);
}
特性 | 支持情况 |
---|
对象属性监听 | ✅(深层递归) |
数组索引监听 | ✅ |
新增/删除属性监听 | ✅ |
Map / Set 响应式 | ✅ |
嵌套对象响应式 | ✅(自动递归) |
防止重复代理 | ✅(内部 WeakMap 缓存) |
用 Proxy 实现 Vue 3 的响应式原理(简化版)
// 简易版 reactive.ts(Vue3 响应式核心原理)// 全局依赖收集容器
let activeEffect: Function | null = null;
const targetMap = new WeakMap<object, Map<string | symbol, Set<Function>>>();// 模拟 Vue3 的 effect
export function effect(fn: Function) {activeEffect = fn;fn(); // 立即执行一次,收集依赖activeEffect = null;
}// 收集依赖
function track(target: object, key: string | symbol) {if (!activeEffect) return;let depsMap = targetMap.get(target);if (!depsMap) {depsMap = new Map();targetMap.set(target, depsMap);}let dep = depsMap.get(key);if (!dep) {dep = new Set();depsMap.set(key, dep);}dep.add(activeEffect); // 添加依赖函数
}// 触发依赖
function trigger(target: object, key: string | symbol) {const depsMap = targetMap.get(target);if (!depsMap) return;const dep = depsMap.get(key);if (dep) {dep.forEach(fn => fn());}
}// 创建响应式对象
export function reactive<T extends object>(target: T): T {return new Proxy(target, {get(obj, key, receiver) {const result = Reflect.get(obj, key, receiver);track(obj, key); // 收集依赖return typeof result === "object" && result !== null? reactive(result) // 深度响应式: result;},set(obj, key, value, receiver) {const oldValue = obj[key as keyof T];const result = Reflect.set(obj, key, value, receiver);if (oldValue !== value) {trigger(obj, key); // 触发更新}return result;}});
}
使用方式(模拟 Vue3 Setup)
import { reactive, effect } from './reactive';const state = reactive({count: 0,nested: {name: 'Vue'}
});effect(() => {console.log('count changed:', state.count);
});effect(() => {console.log('nested.name changed:', state.nested.name);
});state.count++; // 👉 自动触发第一个 effect
state.nested.name = 'V3'; // 👉 自动触发第二个 effect
结果
count changed: 0
nested.name changed: Vue
count changed: 1
nested.name changed: V3
React 为什么不需要 Proxy?
React 的设计理念和 Vue 完全不同:
Vue 是“响应式系统” 修改数据,自动通知 DOM 更新。
React 是“声明式渲染 + 手动状态更新”
组件每次通过 render() 或函数体重新执行,并依赖状态 useState/useReducer/useContext,触发重新渲染。
React 的状态追踪方式
React 不会“监听对象属性变化”,它靠的是手动调用状态更新函数:
const [count, setCount] = useState(0);// 你必须显式调用:
setCount(count + 1);
- 它不劫持对象、也不会追踪谁改了数据;
- 它是“不可变数据 + 手动触发更新”的范式;
- 所以不需要 Proxy 或 defineProperty。
React 不追踪对象属性
const [info, setInfo] = useState({ name: '张三' });info.name = '李四'; // ❌ React 不知道你改了,页面不会更新
- React 根本无法知道你直接改了 info.name,因为它没做数据劫持。
setInfo({ ...info, name: '李四' }); // ✅ 触发更新
React 响应更新靠的机制是
概念 | 内容 |
---|
useState() | 保存状态值(在 Fiber 节点上) |
setState() | 触发调度,让组件重新执行 render |
useEffect() | 执行副作用,类似响应变化 |
虚拟 DOM diff | 每次渲染后对比旧的虚拟 DOM,最小化实际 DOM 操作 |
React 和 Vue 的根本区别
特性 | React | Vue |
---|
状态机制 | 手动触发、不可变数据 | 自动追踪响应式依赖 |
使用 Proxy? | ❌ 不是核心机制 | ✅ Vue 3 核心机制 |
适合大规模 UI 状态变化 | ✅ | ✅ |
对对象属性变更的感知 | ❌ 不感知(除非 setState) | ✅ 自动追踪(Proxy 或 defineProperty) |