写文章不容易,点个赞呗兄弟
专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧
研究基于 Vue版本 【2.5.17】
如果你觉得排版难看,请点击 下面链接 或者 拉到 下面关注公众号也可以吧
【Vue原理】Diff - 源码版 之 从新建实例到开始diff
Diff 的内容很多,我们先来探索一下从 新建实例 到 开始Diff 的流程走一遍,本文很短
先对整个流程有个把握,再仔细去探索 Diff 的思想
首先,当你新建实例的时候,比如这样
你调用一个 Vue 函数,所以来看下 Vue 函数
function Vue() {
... 已省略其他
new Watcher(function() {
vm._update(vm._render());
})
... 已省略其他
}
函数中做了两件事
1、为实例新建一个 watcher
2、为 watcher 绑定更新回调(就是 new Watcher 传入的 function )
每个实例都会有一个专属的 watcher,而绑定的回调,在页面更新时会调用
我们现在来看下简化的 Watcher 的源码
funciton Watcher(expOrFn){
this.getter = expOrFn;
this.get();
}
Watcher.prototype.get = function () {
this.getter()
}
watcher 会保存更新回调,并且在新建 watcher 的时候就会立刻调用一遍更新回调
现在我们继续看 更新回调的内容
vm._update(vm._render());
如果你看到之前的文章应该知道这两个函数的作用
现在就来简单说一下
1vm._render
生成页面模板对应的 Vnode 树,比如
生成的 Vnode 树是( 其中num的值是111 )
{
tag: "div",
children:[{
tag: "span"
},{
tag: undefined,
text: "111"
}]
}
这一步是通过 compile 生成的,具体的话可以简单看下 Compile - 白话版
有兴趣的有耐心的也可以看源码版
2vm._update
比较 旧Vnode 树 和 vm._render 生成的新 Vnode 树 进行比较
比较完后,更新页面的DOM,从而完成更新
ok,我们看下源码
Vue.prototype._update = function(vnode) {
var vm = this;
var prevEl = vm.$el;
var prevVnode = vm._vnode;
vm._vnode = vnode;
// 不存在旧节点
if (!prevVnode) {
vm.$el = vm.__patch__(
vm.$el, vnode,
vm.$options._parentElm,
vm.$options._refElm
);
}
else {
vm.$el = vm.__patch__(
prevVnode, vnode
);
}
};
解释其中几个点
1 vm._vnode
看过 VNode - 源码版 应该知道,这个属性保存的就是当前 Vnode 树
当页面开始更新,而生成了新的 Vnode 树之后
这个属性则会替换成新的Vnode
所以保存在这里,是为了方便拿到 旧 Vnode 树
2 vm.__patch__
是的,没有错,你在两处地方看到这个东西
这个东西就是 Diff 的主要内容,内有乾坤,内容很多,不会在这里说,毕竟今天只探索流程
但是要看看这个东西怎么来的
var patch = createPatchFunction();
Vue.prototype.__patch__ = patch ;
嗯,是经过一个 createPatchFunciton 生成的
然后赋值到 Vue 的原型上,所以可以 vm.__patch__ 调用喽
我们再来说说 _update 函数中出现的那两处 patch
1 不存在旧节点
不需要进行比较,直接全部创建
vm.$el 保存的是 DOM 节点,如果不存在旧节点,那么 vm.$el 此时也是不存在的
而传入 vm.$el 为空的时候,patch 拿到这个值判断为空的时候,就直接创建DOM,不会去做其他操作了
2 存在旧节点
需要把旧节点和新节点比较,尽量找到最小差异部分,然后进行更新,这部分内容就是 Diff 的重点了,需要花费不少精力的。会放在其他文章进行记录
最后
鉴于本人能力有限,难免会有疏漏错误的地方,请大家多多包涵,如果有任何描述不当的地方,欢迎后台联系本人,有重谢