【Vue原理】Diff - 源码版 之 从新建实例到开始diff

2,796 阅读3分钟

写文章不容易,点个赞呗兄弟

专注 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 的重点了,需要花费不少精力的。会放在其他文章进行记录

公众号


最后

鉴于本人能力有限,难免会有疏漏错误的地方,请大家多多包涵,如果有任何描述不当的地方,欢迎后台联系本人,有重谢

公众号