数据劫持
Jul 27, 2022 ·
5 Min Read
使用过
数据劫持:在访问或者修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作或者修改返回结果。最典型的使用场景就是响应式。
- 在
Vue 2.x 利用Object.defineProperty()
。 (具体想知道vue 中怎么实现的可以百度一下,网上都一大把) - 在
Vue 3.x 版本之后改用Proxy 进行实现。
基础
首先说一下两个语法:
Object.defineProperty(obj, prop, descriptor)
该方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
-
obj
要定义属性的对象 -
prop
要定义或修改的属性的名称或Symbol
-
descriptor
要定义或修改的属性描述符 -
返回值:被传递给函数的对象
详情 → 传送门
小
const data = {}
let a = 2;
Object.defineProperty(data, 'a', { enumerable: true, configurable: false, get: () => { console.log('get:', a) return a }, set: (newValue) => { console.log('set:', newValue) a = newValue }})
data.a = 10console.log(data.a)
// 输出:// set: 10// get: { a: 10 }// 10
new Proxy(target, handler)
target
要使用Proxy
包装的目标对象(可以使任何类型的对象,包括原生数组,函数,甚至另一个代理) 。handler
一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理p
的行为。
详情 → 传送门
小
const handle = { get(obj, prop, value) { console.log('get:', value) return value }, set(obj, prop, value) { obj[prop] = value console.log('set:', value) return true }}
const data = { a: 1}
const proxy = new Proxy(data, handle);proxy.a = 10console.log(proxy.a)
// 输出:// set: 10// get: { a: 10 }// 10
Object.defineProperty
对比 Proxy
Object.defineProperty
的问题有三个:
- 不能监听数组的变化(如:
push, pop, shift, unshift,splice, sort, reverse 并不会触发set ) - 不能监听对象,即必须遍历对象的每个属性
。 (通常配合Object.keys
来遍历) - 必须深层遍历嵌套的对象
Proxy
优点:
- 可以监听整个对象,而不是对象的某个属性,但深层对象还是无法监听。
- 支持数组。
- 支持更多的捕捉器
- 会受浏览器厂商更多的关注和性能优化
缺点:
ES6 新语法- 无法使用
polyfill 来兼容(即兼容性需要考虑)
利用 Proxy
实现类似
class Observe { constructor(data) { this.handle = { set: (target, property, value) => { console.log('set:', value) // 判断新值是否等于旧值 if (target[property] === value) { return false; } target[property] = value; // TODO 数据变化,通知所有订阅者 return true; }, get: (target, property) => { console.log('get:', target[property]) return target[property]; } } // 监听每一个数据 this.data = this.defineReactive(data); }
// 利用proxy监听data,循环遍历监听data下所有对象,并返回proxy // 注:因为proxy与Object.defineProperty不同,可以监听一个对象,但一个对象的深层对象会监听不到,所有需要遍历监听所有引用类型的。 defineReactive(data) { if (!data || typeof data !== "object") { return data; }
Object.keys(data).forEach(key => { data[key] = this.defineReactive(data[key]); });
return new Proxy(data, this.handle); }}
const data = { a: 1, b: { c: 2, d: { e: 3 } }}
const proxy = new Observe(data).data
console.log(proxy.b.d.e)proxy.b.d.e = 10console.log(proxy.b.d.e)
// 输出:// 第一步操作 console.log(proxy.b.d.e)// get: { c: 2, d: { e: 3 } }// get: { e: 3 }// get: 3// 3// 第二步操作 proxy.b.d.e = 10// get: { c: 2, d: { e: 3 } }// get: { e: 3 }// set: 10// 第三步操作 console.log(proxy.b.d.e)// get: { c: 2, d: { e: 10 } }// get: { e: 10 }// get: 10// 10
Last edited Feb 15