数据劫持
Jul 27, 2022 ·
5 Min Read
使用过 Vue 框架的应该就不陌生了,因为 Vue 中的响应式原理就是 数据劫持 和 订阅发布模式。而这里主要讲的是 数据劫持。
数据劫持:在访问或者修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作或者修改返回结果。最典型的使用场景就是响应式。
- 在 Vue 2.x 利用
Object.defineProperty()
。(具体想知道 vue 中怎么实现的可以百度一下,网上都一大把) - 在 Vue 3.x 版本之后改用 Proxy 进行实现。
基础
首先说一下两个语法:
Object.defineProperty(obj, prop, descriptor)
该方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
-
obj
要定义属性的对象 -
prop
要定义或修改的属性的名称或Symbol
-
descriptor
要定义或修改的属性描述符 -
返回值:被传递给函数的对象
详情 → 传送门
小DEMO
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)
Proxy 对象用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)。
target
要使用Proxy
包装的目标对象(可以使任何类型的对象,包括原生数组,函数,甚至另一个代理)。handler
一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理p
的行为。
详情 → 传送门
小DEMO
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
实现类似 Vue 中的数据挟持
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