Vue的主要原理中主要用到了定义的这么几个函数Dep,Watcher,observer。

我们来使用这几个函数简单的实现一下vue构造函数数据绑定和相互依赖部分,梳理一下它们之间的关系。

省略了编译部分和proxy代理与其他的一些复杂逻辑。

Dep

Dep是依赖类,简要实现为


class Dep {
constructor () {
// 放当时属性的观察者
this.subs = []
}
}
// target 用来挂载当时的watcher观察者
Dep.target = null


observer

做属性劫持,并做点其他事情



function observer (vm, key, val) {
let dep = new Dep()
Object.defineProperty(vm, key, {
/**
* get主要做两个事情
* 1. 收集观察当前key的wathcer(即依赖当前key的操作)
* 2. 获取值
*/
get () {
// 这是作用1
if (Dep.target) {
dep.subs.push(Dep.target)
}
// 这是作用2
return val
},
/**
* set也是两个事情
* 1. 修改目标值
* 2. 执行依赖当前key的watcher
*/
set (newVal) {
// 这是作用1
val = newVal
// 这是作用2
for(cb of dep.subs) {
cb.call(vm)
}
}
})
}


Watcher

Watcher是观察者类,用来创建依赖某属性的操作(如指令,渲染,计算属性等)


class Watcher {
/**
* vm: 实例
* cb: 依赖某属性的操作函数
*/
constructor (vm, cb) {
// 把当前的操作挂载到Dep上
Dep.target = cb
/**
* 执行操作,两个作用
* 1. 进行操作的初始化
* 2. 触发属性的get方法,使当前cb被收集
*/
cb.call(vm)
Dep.target = null
}
}


demo

那么我们就使用上面定义好的函数写个例子

```<div> <p class="text"></p> <div> ```


let vm = new Vue({
// 假设有data
data: {msg: 1},
// 有某个v-text操作,我们抽象为vText函数,依赖属性msg(代表所有依赖其他属性的操作)
renderFun: {
vText () {
document.querySelector('.text').innerText = this.msg
}
}
})
// 修改vue实例的值,观察变化
vm.msg = 333


那么我们也写一个vue的简易构造函数


class Vue {
constructor (options) {
let data = options.data
let renderFun = options.renderFun
// initData
Object.keys(data).forEach(key => {
observer(this, key, data[key])
})
// 模拟计算属性,watcher,指令等依赖属性的操作
Object.keys(renderFun).forEach(key => {
new Watcher(this, renderFun[key])
})
}
}


执行过程

完整的代码可以看demo部分的两个链接

  1. 创建vue实例,执行​​new Vue()​
  2. 对data进行初始化,对data中属性进行属性劫持
  • 劫持过程中,在闭包内创建对当前属性的依赖队列(dep.subs)和值(val)。​​get​​进行观察者​​watcher​​的收集和值得获取;​​set​​进行值的更新和依赖队列中​​watcher​​的执行
  1. 对编译过程中如​​computed\watcher​​或​​模板编译​​过程中的​​指令​​函数进行初始化,我们以​​renderFun​​代替
  2. 针对​​renderFun​​中的每个功能函数进行​​new Watcher()​​工作
  3. vText为例子,在new Wathcer()过程中
  1. 将​​vText​​挂载到全局通用的​​Dep.target​​上
  2. 执行​​vText​​,其中有读​​vm.msg​​的操作,则触发msg属性的get,进入​​Dep.target​​判断,将​​Dep.target​​即​​vText​​收集进​​msg​​的​​subs​​依赖队列中,此时​​vText​​执行完毕,页面​​innetText​​被修改
  3. 将​​Dep.target​​置空
  1. 执行vm.msg = 333,则触发msgset
  1. ​set​​先修改​​msg​​的值
  2. 再执行​​msg​​依赖队列中的所有​​watcher​​的函数,即​​vText​​,页面的​​innerText​​被同步更新
总结

总之几者的关系就是在​​observer​​的​​get​​中将对当前属性的​​watcher​​收集进​​dep​​,在​​observer​​的​​set​​中执行收集到的​​watcher​​。

而vue的真正的执行过程绝不是上面写的这么简单,比如watcher的执行就绝不是简单的遍历执行,而且还对observer进行了很大程度的简化。我们还省略了诸如​​_proxy​​、​​defineReactive​​等出现频率较高的函数。写这样一个最简实现主要是为了梳理一下主干,降低阅读源码的难度。