1.什么是变化侦测
Vue会自动通过状态生成DOM,并将其输出到页面上显示出来,这个过程叫渲染。Vue的渲染过程是声明式的,我们通过模板来描述状态与DOM之间的映射关系。
通常,在运行时应用内部的状态是不断发生变化的,此时需要不停地重新渲染。这时如何确定状态中发生了什么变化?变化侦测就是来解决这个问题的,它分为两种形式,一种是推(push)一种是拉(pull)。
vue的变化侦测属于推。当状态发生变化时,Vue立刻就知道了,而且在一定程度上知道哪些状态发生变化了。因此,它知道的信息更多,也就可以进行更深粒度的更新。
2.如何追踪变化
如何侦测变化?
.在js中,可以使用Object.defineProperty来侦测对象的变化,那么就可以写出这样的代码:
function defineReactive(data, key, val){
Object.defineProperty(data, key ,{
enumerable : true,
configurable : true,
get : function () {
return val;
},
set : function (newVal) {
if (val == newVal){
return;
}
val = newVal;
}
})
}
这里的函数defineReactive用来对Object.defineProperty进行封装。从函数的名字可以看出,其作用是定义一个响应式数据。也就是在这个函数中进行变化追踪,封装后只需要传递data、key、value 即可。
3.如何收集依赖
如果只是把Object.defineProperty进行封装,那其实并没有什么作用
,其实真正有用的是收集依赖:
先看一个例子:
<template>
<h1>{{name}}</h1>
</template>
这个例子中使用了数据name,所以当它发生变化时,要向使用了它的地方发送通知。
先收集依赖,把用到数据name的地方收集起来,然后等属性发生变化时,把用到数据name的地方收集起来,然后等属性发生变化时把之前收集好的依赖循环触发一遍。
即在getter中收集依赖,在setter中触发依赖。
4.依赖收集在哪里
假设依赖是一个函数,保存在window.target中,就可以把defineReactive函数稍微改造一下:
function defineReactive(data, key, val){
let dep = [];//new
Object.defineProperty(data, key ,{
enumerable : true,
configurable : true,
get : function () {
dep.push(window.target)//new
return val;
},
set : function (newVal) {
if (val == newVal){
return;
}
//新增
for(let i = 0; i < dep.length; i++){
dep[i](newVal, val)
}
val = newVal;
}
})
}
这里新增了数组dep,用来储存被收集的依赖。
然后在set被触发时,循环dep用来储存被收集的到的依赖、然后在set被触发时,循环dep以触发收集到的依赖。
也可以将依赖收的代码封装成一个dep类,它专门帮我们管理依赖。使用这个类,我们可以收集依赖、删除依赖或向依赖发送通知等。
export default class Dep {
constructor() {
this.subs = [];
}
addSub(sub){
this.subs.push(sub)
}
removeSub(sub){
remove(this.subs.sub)
}
depend(){
if (window.target){
this.addSub(window.target)
}
}
notify(){
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++){
subs[i].update()
}
}
}
function remove(arr, item){
if (arr.length){
const index = arr.indexOf(item)
if (index >= -1){
return arr.splice(index, 1)
}
}
}
function defineReactive(data, key, val){
let dep = new Dep();//new
Object.defineProperty(data, key ,{
enumerable : true,
configurable : true,
get : function () {
dep.depend()//new
return val;
},
set : function (newVal) {
if (val == newVal) {
return;
}
val = newVal;
dep.notify();//new
}
})
}
5.依赖是谁
在上面的代码中,我们收集的依赖是window.target,那么它到底是什么?究竟要研究什么?
收集谁,也就是当属性发生变化后通知谁。
我们要通知用到的数据的地方,而使用这个数据的地方有很多,而且类型还不一样,既有可能是模板,也有可能是用户写的一个watch,这时需要抽象出一个能集中处理这些情况的类。然后,在依赖收集阶段只收集这个封装好的类的实例进来,通知也只通知它一个,接着,它再负责通知其他地方。
本文参考:深入浅出Vue.js/刘博文著