手写Vue数据劫持
基于 Vue2 版本
# 对象劫持
# 原理 Object.defineProperty()
函数签名: 参考 MDN
语法
Object.defineProperty(obj, prop, descriptor)
参数
obj: 要定义属性的对象。
prop: 要定义或修改的属性的名称或 Symbol 。
descriptor : 要定义或修改的属性描述符。
返回值
被传递给函数的对象。
举个例子:
var message = 'hello world'
const data = {}
Object.defineProperty(data, 'message', {
get() {
return message
},
set(newVal) {
message = newVal
}
})
data.message // 'hello world'
data.message = 'test' // 'test'
2
3
4
5
6
7
8
9
10
11
12
当我们读取或者设置被定义的属性时,就会执行 get 或者 set 方法,也就是在这两个方法中,我们实现数据劫持,可以在这里添加一些我们附加的操作,而不只是简单的读写。
那么在 vue 中,是怎么实现对 data 中所有的属性做到数据劫持的呢?其实原理都一样,我们只需遍历所有 data 对象中的所有属性,并对每一个属性使用 Object.defineProperty 劫持即可,当属性的值发生变化的时候,我们执行一系列的渲染视图的操作。
下面我模拟实现 Vue 数据劫持的过程。
const data = {
name: '小明',
age: 25,
info: {
address: '上海'
}
}
function renderView(name, newVal) {
console.log(name + ' 更新为了 ' + newVal + ' ,视图正在更新。。。')
}
function observerObject(target, name, value) {
if (typeof value === 'object' || Array.isArray(target)) {
observer(value)
}
Object.defineProperty(target, name, {
get() {
return value
},
set(newVal) {
if (newVal !== value) {
if (typeof value === 'object' || Array.isArray(value)) {
observer(value)
}
value = newVal
}
renderView(name, newVal)
}
})
}
function observer(target) {
if (typeof target !== 'object' || !target) {
return target
}
for (const key in target) {
if (target.hasOwnProperty(key)) {
const value = target[key]
observerObject(target, key, value)
}
}
}
observer(data)
data.name = '帅锅'
data.info.address = '成都'
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
这段代码执行后控制台输出
// name 更新为了 帅锅 ,视图正在更新。。。
// address 更新为了 成都 ,视图正在更新。。。
2
# 数组劫持
Object.defineProperty 只能够作用在对象上,那么 vue 中,对数组是怎么实现数据劫持的呢? 只需要修改数组的原型方法,往这些方法里添加一些视图渲染的操作。
const oldArrayProperty = Array.prototype
const newArrayProperty = Object.create(oldArrayProperty)
;['pop', 'push', 'shift', 'unshift', 'splice', 'reverse'].forEach((method) => {
newArrayProperty[method] = function() {
oldArrayProperty[method].call(this, ...arguments)
renderView()
}
})
// 在observer函数中加入数组的判断,如果传入的是数组,则改变数组的原型对象为我们修改过后的原型。
if (Array.isArray(target)) {
target.__proto__ = newArrayProperty
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
这样在调用数组的这些方法时就会及时调用 renderView 更新视图
# 总结
上面代码就是 vue2x 版本数据劫持的原理实现(和源代码区别很大但原理相同)。
对象劫持
现在我们可以看出 Object.defineProperty 的一些问题
递归遍历所有的对象的属性,这样如果我们数据层级比较深的话,是一件很耗费性能的事情
只能应用在对象上,不能用于数组
只能够监听定义时的属性,不能监听新加的属性,这也就是为什么在 vue 中要使用 Vue.set 的原因,删除也是同理
数组劫持
数组劫持也能通过遍历去劫持每一个 item, 然而 Vue 采用的是修改原新方法方式实现, 因为一般开发中,对象属性多的最多几十个,量级小,而数组可能成百上千上万,如果每个 item 都劫持太耗费性能且不可控。