文章目录
前言
最近在学习vue源码,在网上看到很多大神的博客,看起来感觉还是很吃力的,所以自己也记录一下加深理解,俗话说:“好记性,不如烂笔头”。
整体过程
vue实例化对象的具体过程:
- 新创建一个实例后,Vue调用compile将el转换成vnode。
- 调用initState, 创建props, data的钩子以及其对象成员的Observer(添加getter和setter)。
- 执行mount挂载操作,在挂载时建立一个直接对应render的Watcher,并且编译模板生成render函数,执行vm._update来更新DOM。
- 每当有数据改变,都将通知相应的Watcher执行回调函数,更新视图
- 当给这个对象的某个属性赋值时,就会触发set方法 。
- set函数调用,触发Dep的notify()向对应的Watcher通知变化。
- Watcher调用update方法。
在这个过程中具体讲解:
- Obsever是给数据添加依赖的Dep.
- Dep是data每个对象包括子对象都拥有一个该对象, 当所绑定的数据有变更时, 通过dep.notify()通知Watcher。
- Compile是HTML指令解析器,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数。
- Watcher是连接Observer和Compile的桥梁,Compile解析指令时会创建一个对应的Watcher并绑定update方法 , 添加到Dep对象上。
Object.defineProperty
如果不了解这个属性的话可以查看链接:添加链接描述
在vue2.0中大家用该都了解Vue的响应式原理是通过Object.defineprototype来实现的,被Object.defineprototype绑定过得对象,会变成【响应式】化,也就是改变这个对象,会触发get和set事件,进而触发某些视图的更新,举个例子:
let data = {
msg: 'hello',
count: 1222
}
let vm = {};
Object.defineProperty(vm, 'msg', {
enumerable: true,
configurable: true,
get() {
console.log('get', data.msg);
},
set(newVlue) {
console.log('get', newVlue);
if (newVlue == data.msg) {
return
}
data.msg = newVlue;
document.querySelector('#container').textContent = data.msg
}
})
vm.msg = 444;
Observer【响应式】
Observer就是用来管理上边Object.defineProperty的过程,我们用一下代码来描述:
class Observer {
constructor() {
//响应式绑定数据的方法
observe(this.data)
}
}
function observe(data) {
const keys = Object.keys(data);
for (let i = 0; i < keys.length; i++) {
//将data中的每个属性都进行绑定。
defineReactive(obj, keys[i])
}
}
Dep【依赖管理】
那么什么是依赖?
那Dep究竟是用来做什么的呢? 我们通过defineReactive方法将data中的数据进行响应式后,虽然可以监听到数据的变化了,那我们怎么处理通知视图就更新呢?
Dep就是帮我们收集【究竟要通知到哪里的】。比如下面的代码案例,我们发现,虽然data中有text和message属性,但是只有message被渲染到页面上,至于text无论怎么变化都影响不到视图的展示,因此我们仅仅对message进行收集即可,可以避免一些无用的工作。
那这个时候message的Dep就收集到了一个依赖,这个依赖就是用来管理data中message变化的。
<div>
<p>{{message}}</p>
</div>
data:{
msg:"hello world",
count:1
}
当时用watch属性时,也就是开发者自定义的监听某个data中属性的变化,比如监听message的变化,message变化时就会通知watch这个钩子,让它去执行这个函数。
这个时候message的Dep就收集到了两个依赖,第二个依赖就是用来管理watch中message变化的。
watch:{
message:function(newValue,oldValue){
console.log(newValue+','+oldValue)
}
当开发者自定义computed计算属性时,如下messageT属性,是依赖message的变化的。因此message变化时我们也要通知到computed,让它去执行回调函数。
这个时候message的Dep就收集到了三个依赖,这个依赖就是用来管理computed中message变化的。
computed:{
message(){
return this.message+'....'
}
}
图示如下:一个属性可能有多个依赖,每个响应式数据都有一个Dep来管理它的依赖。
如何收集依赖
我们如何知道data中的某个属性被使用了,答案就是Object.defineProperty,因为读取某个属性就会触发get方法。可以将代码进行如下改造:
function defineReactive() {
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter(newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// #7981: for accessor properties without setter
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
那所谓的依赖究竟是什么呢?上面的图中已经暴露了答案,就是Watcher。
源码分析:
// Dep是订阅者Watcher对应的数据依赖
var Dep = function Dep () {
//每个Dep都有唯一的ID
this.id = uid++;
//subs用于存放依赖
this.subs = [];
};
//向subs数组添加依赖
Dep.prototype.addSub = function addSub (sub) {
this.subs.push(sub);
};
//移除依赖
Dep.prototype.removeSub = function removeSub (sub) {
remove(this.subs, sub);
};
//设置某个Watcher的依赖
//这里添加了Dep.target是否存在的判断,目的是判断是不是Watcher的构造函数调用
//也就是说判断他是Watcher的this.get调用的,而不是普通调用
Dep.prototype.depend = function depend () {
if (Dep.target) {
Dep.target.addDep(this);
}
};
Dep.prototype.notify = function notify () {
var subs = this.subs.slice();
//通知所有绑定 Watcher。调用watcher的update()
for (var i = 0, l = subs.length; i < l; i++) {
subs[i].update();
}
};
Watcher【中介】
Watcher就是类似中介的角色,比如message就有三个中介,当message变化,就通知这三个中介,他们就去执行各自需要做的变化。
Watcher能够控制自己属于哪个,是data中的属性的还是watch,或者是computed,Watcher自己有统一的更新入口,只要你通知它,就会执行对应的更新方法。
因此我们可以推测出,Watcher必须要有的2个方法。一个就是通知变化,另一个就是被收集起来到Dep中去。
class Watcher {
addDep() {
// 我这个Watcher要被塞到Dep里去了~~
},
update() {
// Dep通知我更新呢~~
},
}
总结
回顾一下,Vue响应式原理的核心就是Observer、Dep、Watcher。
Observer中进行响应式的绑定,在数据被读的时候,触发get方法,执行Dep来收集依赖,也就是收集Watcher。
在数据被改的时候,触发set方法,通过对应的所有依赖(Watcher),去执行更新。比如watch和computed就执行开发者自定义的回调方法。