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); // 123
user.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) |