数据劫持通过Object.defineProperty(ES5)或Proxy(ES6+)实现,前者需逐个定义属性且不支持动态增删,后者可全面代理对象并拦截13种操作;实际使用需注意兼容性、基本类型限制及无限递归等问题。

利用原生 getter 与 setter 实现数据劫持,本质是通过 Object.defineProperty(ES5)或 Proxy(ES6+)拦截对对象属性的读取和赋值操作,在不修改业务代码的前提下注入响应式逻辑。目前主流框架(如 Vue 2 使用 defineProperty,Vue 3 使用 Proxy)都基于这一思想,但手动实现时需注意兼容性与限制。
使用 Object.defineProperty 劫持单个属性
这是 ES5 提供的标准方式,适用于已知属性名、且不要求动态新增/删除属性的场景。
- 必须对每个属性单独定义,无法监听对象新增属性或数组索引变化
- 需要深度遍历嵌套对象,对每个可枚举属性递归调用
defineProperty - 不能监听
for...in、Object.keys()等枚举行为,仅拦截.prop和obj[prop]形式的访问
示例:
const data = { count: 0, name: 'Alice' };
Object.keys(data).forEach(key => {
let value = data[key];
Object.defineProperty(data, key, {
get() {
console.log(`读取 ${key}:`, value);
return value;
},
set(newVal) {
console.log(`设置 ${key} 为`, newVal);
value = newVal;
// 这里可触发更新,如通知视图重绘
updateView();
}
});
});
用 Proxy 全面代理整个对象
Proxy 是 ES6 引入的更强大、更通用的代理机制,能拦截 13 种操作,天然支持数组、动态属性、继承等,是现代数据劫持的首选。
- 一次代理整个对象,无需逐个定义属性
- 可捕获属性新增(
set)、删除(deleteProperty)、枚举(ownKeys)、in操作符等 - 对数组索引赋值、
push/pop等方法也能通过set拦截(但需配合重写原型方法才能完全响应)
示例:
const data = { count: 0, items: [] };
const reactive = new Proxy(data, {
get(target, key, receiver) {
console.log('get', key);
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
console.log('set', key, value);
const result = Reflect.set(target, key, value, receiver);
updateView();
return result;
}
});
注意事项与常见陷阱
无论用哪种方式,实际工程中都要规避典型问题:
-
defineProperty对于原始值(如字符串、数字)的包装对象才有效;直接代理基本类型会报错 - Proxy 返回的是新对象,原对象不受影响,需确保后续代码使用代理后的对象
- Proxy 无法被 polyfill,IE 完全不支持;
defineProperty在 IE9+ 可用,但不支持数组索引劫持 - 避免无限递归:在
set中修改自身属性时,若未用Reflect.set或绕过代理,可能触发死循环
简单封装一个响应式函数(Proxy 版)
便于复用,同时处理嵌套对象的响应式转换:
function reactive(obj) {
if (!obj || typeof obj !== 'object') return obj;
return new Proxy(obj, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver);
// 如果是对象,递归代理(惰性代理)
if (res && typeof res === 'object') {
return reactive(res);
}
return res;
},
set(target, key, value, receiver) {
const oldVal = target[key];
const result = Reflect.set(target, key, value, receiver);
if (oldVal !== value) updateView(); // 触发更新
return result;
}
});
}

















