vue1.0 源码解析二:理解 vue 的架构
Opened this issue · 0 comments
什么是vuejs
这里更多指的是 原版的vuejs的架构,当然tinyvue也是一样的。
我个人对vuejs的定义是:通过 directives 实现 data 和 DOM 的关联的一个框架。
如果用一张图来定义大概是这样
更复杂点是这样的:
当然这里的data是广义的,包括初始化组件时传入的 data, props methods, computed 等一系列的方法和属性,把这些东西和DOM做关联,做到联动,是vuejs的核心功能。
数据响应化
这里说的是data的响应化,props暂时我们不去管。
从 /instance/vue.js
开始看,会发现他的除了 init 之外,第一个调用的是 stateMixin
stateMixin主要做了两件事:
- proxy, 把对
this.xxx
的访问代理到对this._data.xxx
上,通过 getter 和 setter 实现的 - observe(this._data) 把this._data 变成响应式的数据
observe(this._data) 做了什么呢,它主要是对传入的data设置了 getter 和 setter,这样对data的访问会先触发对应的getter/setter,因此当数据有读写操作的时候都可以检测到
可以看一下一个很有意思的细节:
observer/index.js
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 属性的。
所以如果以后你面试的时候,面试官问你Vue是怎么收集对数据的依赖的,你应该知道该怎么回答了。
Directive 指令
Directive的实现要比data更复杂一些。我画了一个图:
按照Directive的生命周期来说:
- 在compile阶段,会通过 el.attributes 来获取所有的attributes,并且通过正则匹配name的方式筛选出类似
v-xxx
的指令,生成一个指令的描述descriptor
,这个描述就包含了name, value, def 等等以后需要创建指令的时候传入的参数 - 在 _bindDir 阶段会根据上一步收集的指令描述符来创建指令
new Directive
,并最终逐个调用指令的 ‘_bind” 方法 - 在指令的 _bind 方法中,会创建
Watcher
监听对应的表达式,比如v-text=“name”
就会监听name
表达式,当 this.name 改变时,会由 Observer 通过 dep 来通知 watcher,然后 watcher 调用 directive 的update方法。
这里有个很有意思的事情,watcher是为了监听一个值而创建的,但是watcher本身会把这个值存起来,所以后面访问的时候不是vm.name
而是watcher.value
的形式访问的。
Dep 存在意义就是,他记录了 Watcher
和 Observer
之间的依赖关系,是二者的一个桥梁。
源码中在compile的时候有很多这样的link回调:
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 的时候就会懂了。