八小时实现迷你版vuejs二 vuejs架构

什么是vuejs

这里更多指的是 原版的vuejs的架构,当然tinyvue也是一样的。
我个人对vuejs的定义是:通过 directives 实现 data 和 DOM 的关联的一个框架。

如果用一张图来定义大概是这样

vuejs 架构

更复杂点是这样的

vuejs 架构

当然这里的data是广义的,包括初始化组件时传入的 data, props methods, computed 等一系列的方法和属性,把这些东西和DOM做关联,做到联动,是vuejs的核心功能。

数据响应化

这里说的是data的响应化,props暂时我们不去管。
/instance/vue.js 开始看,会发现他的除了 init 之外,第一个调用的是 stateMixin

stateMixin主要做了两件事:

  1. proxy, 把对 this.xxx 的访问代理到对 this._data.xxx 上,通过 getter 和 setter 实现的
  2. observe(this._data)this._data 变成响应式的数据

observe(this._data) 做了什么呢,它主要是对传入的data设置了 getter 和 setter,这样对data的访问会先触发对应的getter/setter,因此当数据有读写操作的时候都可以检测到

3

可以看一下一个很有意思的细节:
observer/index.js

1
2
3
4
5
6
7
8
9
10
11
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
var value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
//...
}
return value
}

其中 Dep.target 是一个全局的属性,每当 watcher.get 调用的时候就会把自己设置上去,调用完就取消。
所以如果存在 Dep.target,那么说明当前有一个watcher是从这里取值的,也就是这个watcher依赖这个dep,于是就有了 dep.depend(),会把watcher加入deps依赖。

这是一个很巧妙的设计:在getter中收集依赖。比如 v-text=“name” 那么肯定会调用一次getter 获取name,因此就可以确定 这个指令是依赖 name 属性的。

Directive

Directive的实现要比data更复杂一些。我画了一个图:

4

按照Directive的生命周期来说:

  1. 在compile阶段,会通过 el.attributes 来获取所有的attributes,并且通过正则匹配name的方式筛选出类似 v-xxx 的指令,生成一个指令的描述 descriptor,这个描述就包含了name, value, def 等等以后需要创建指令的时候传入的参数
  2. 在 _bindDir 阶段会根据上一步收集的指令描述符来创建指令 new Directive,并最终逐个调用指令的 ‘_bind” 方法
  3. 在指令的 _bind 方法中,会创建 Watcher 监听对应的表达式,比如 v-text=“name” 就会监听 name 表达式,当 this.name 改变时,会由 Observer 通过 dep 来通知 watcher,然后 watcher 调用 directive 的update方法。
    这里有个很有意思的事情,watcher是为了监听一个值而创建的,但是watcher本身会把这个值存起来,所以后面访问的时候不是 vm.name 而是 watcher.value 的形式访问的。

Dep 存在意义就是,他记录了 WatcherObserver 之间的依赖关系,是二者的一个桥梁。

源码中在compile的时候有很多这样的link回调:

1
2
3
4
5
6
7
8
9
10
return function compositeLinkFn (vm, el, host, scope, frag) {
// cache childNodes before linking parent, fix #657
var childNodes = toArray(el.childNodes)
// link
var dirs = linkAndCapture(function compositeLinkCapturer () {
if (nodeLinkFn) nodeLinkFn(vm, el, host, scope, frag)
if (childLinkFn) childLinkFn(vm, childNodes, host, scope, frag)
}, vm)
return makeUnlinkFn(vm, dirs)
}

返回的函数套着函数,很容易让人一头雾水,其实这样做主要是因为在 link 的时候依赖 scope 等参数,其实并没有传给compile,因此需要返回一个函数,在需要时候再调用这个函数并传入对应的参数。

关于 vuejs的架构先讲这么多,没有理解没关系,下面我们在动手写 tinyvue 的时候就会懂了。