首先,给Watcher对象做点改动:
Watcher.prototype.update = function () {
// 若是lazy Watcher, 只需把dirty设置为true,否则若sync为true,直接执行run,否则就在nextTick中执行run方法,在这里暂时不考虑queueWatcher情况
if (this.lazy) {
this.dirty = true
} else {
this.run()
}
}
Watcher.prototype.run = function () {
const oldValue = this.value
// 每次执行run方法时都会调用get方法重新收集依赖
const value = this.get()
this.value = value
// 执行watcher的回调
if (this.cb) {
this.cb.call(this.vm, value, oldValue)
}
}
// 在dep.depend中调用
Watcher.prototype.addDep = function (dep) {
if (!this.depIds.has(dep.id)) {
this.deps.push(dep)
this.depIds.add(dep.id)
dep.addSub(this)
}
}
可以看到,在每次Watcher.run执行的时候,都会执行this.get方法获取最新的值,而执行get方法就会重新收集该watcher的相关依赖,那么问题就来了,每次依赖的数据发生改变,都会重新触发依赖收集,那么该数据的dep中就会存在重复的watcher。所以我们为Dep加上uid的静态属性,每次实例化时候+1,在Watcher依赖收集的时候判断是否已经存在该dep,避免重复的依赖收集。
另外,在vue的源码中,Watcher中还有newDepIds,newDeps这两个属性,据我理解,是为了重新依赖收集时去除没必要的依赖,这里暂不实现。
重点来了
要怎样收集页面渲染所需的依赖,使页面渲染相关的数据发生改变就触发页面的重新渲染呢。其实关键的代码就这么一句
vm._watcher = new Watcher(vm, updateComponent)
这样,updateComponent需要用到的相关数据就会被收集到render Watcher(vm._watcher)中,每次数据发生改变就会重新调用updateComponent方法。
VNode
关于VNode部分,暂时只实现一些目前需要用到的东西
var VNode = function (tag, data, children, text, elm, context) {
this.tag = tag
this.data = data
this.children = children
this.text = text
this.elm = elm
this.context = context
}
function createTextVNode (val) {
return new VNode(undefined, undefined, undefined, val)
}
Vue.prototype.$createElement = function (tag, data, children) {
var text, vnode, _children = children || []
// 处理render函数中的文字
_children.forEach(function (item, n) {
if (typeof item === 'string' || typeof item === 'number') {
_children.splice(n, 1, createTextVNode(item))
}
})
if (typeof tag === 'string') {
vnode = new VNode(tag, data, _children)
}
return vnode
}
都知道,vue包括完整版和运行时两个版本,区别在于完整版包括运行时+编译器,就是把模板编译成render函数,因此编译器对于vue来说并不是绝对必要的,这里暂不去实现。
如果是运行时版本,那么就需要自己直接写render函数了,关于render函数可参考官方文档。以上代码就可以把render函数处理成VNode对象。
附上本文详细的代码与注释(相关代码)
网友评论