vue的数据劫持
vue的数据劫持和简述过程
Object.defineProperty
提到数据劫持,首先想到的就是Object.defineProperty;Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。先来看一下Object.defineProperty(obj, prop, descriptor)的参数。
obj:要定义属性的对象;
prop:要定义和修改的属性的名称或Symbol;
descriptor:要定义或修改的属性描述符(数据描述符和存取描述符)。
详情可看:Object.defineProperty() - JavaScript | MDN
通过Object.defineProperty
绑定的对象,会变成响应式化。利用的是Object.defineProperty
中第三个参数的存取描述符;也就是在改变对象时触发get和set方法来触发一系列视图更新。看例子:
function defineReactive (obj, key, val) {
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: () => {
console.log('我被读了,我要不要做点什么好?');
return val;
},
set: newVal => {
if (val === newVal) {
return;
}
val = newVal;
console.log("数据被改变了,我要把新的值渲染到页面上去!");
}
})
}
let data = {
text: 'hello world',
};
// 对data上的text属性进行绑定
defineReactive(data, 'text', data.text);
console.log(data.text); // 控制台输出 <我被读了,我要不要做点什么好?> 和 hello world
data.text = 'hello Vue'; // 控制台输出 <数据被改变了,我要把新的值渲染到页面上去!> 和 hello Vue
从例子中可以看到当data.text的值发生变化的时候就会触发set这个事件打印<数据被改变了,我要把新的值渲染到页面上去!>,如果他的值没有发生变化机会触发get这个事件打印<我被读了,我要不要做点什么好?>。在vue中则是将这个响应式的Object.defineProperty放到了Observer类中进行管理。
//源码
class Observer {
constructor (value) {
this.value = value // 传入值
this.dep = new Dep()
if (Array.isArray(value)) { // 判断是否是数组
this.observeArray(value)
} else {
this.walk(value)
}
}
walk (obj) { // 非数组
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i]) // 调用响应式事件
}
}
observeArray (items) { // 数组的方法
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i]) // 递归调用Observer
}
}
}
从源码中可以清晰地看出,通过Observer这个类将vue中this.date里的属性进行响应式的绑定。
接下来就是依赖收集:
我们通过defineReactive
方法将data
中的数据进行响应式后,虽然可以监听到数据的变化了,那我们怎么处理通知视图就更新呢?
在Vue中有一个Dep,它就是帮助我们进行依赖的收集。比如
<div>
<p> {{message}} </p>
</div>
data: {
name: '舒楠',
message: '依赖被收集'
}
在这例子中,虽然data中有两个属性,但是只有message属性被渲染到页面上,因此我们只用通过dep收集message属性既可,可以避免无用的消耗。
响应式代码进行如下修改:
function defineReactive (obj, key, val) {
let Dep; // 依赖
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: () => {
console.log('我被读了,我要不要做点什么好?');
// 被读取了,将这个依赖收集起来
Dep.depend(); // 本次新增,进行依赖的收集哪些属性需要用到
return val;
},
set: newVal => {
if (val === newVal) {
return;
}
val = newVal;
// 被改变了,通知依赖去更新
Dep.notify(); // 本次新增,进行依赖的更新
console.log("数据被改变了,我要把新的值渲染到页面上去!");
}
})
}
dep实现的是收集依赖和通知更新,具体dep的执行过程不做详细解释(后期会做)。
最后就是Watcher
它是一个观察者的实例;创建实例的时候会把实例添加到dep中,简单的来说就是Watcher
能够控制自己属于哪个,是data
中的属性的还是watch
,或者是computed
,Watcher
自己有统一的更新入口,只要你通知它,就会执行对应的更新方法。(具体后期会做)。
总结
核心说的数据劫持,其实就是通过Object.defineProperty
中的descriptor的存取描述符进行数据的劫持。
get事件在属性没有变化时触发并且还会触发dep收集依赖,set事件在属性发生变化触发并且触发dep收集依赖再触发Watch执行更新,这个两个事件在一起就实现了vue中Observer的数据劫持。
更多推荐
所有评论(0)