大家好,在网上看到过很多vue源码的解析,大多是copy和摘抄的,少有自己的理解,所以下决心花时间整理一遍自己对vue源码的理解。我只是一个普普通通的码农,如果本文有写的不对之处,希望各路大神指正,谢谢!
vue版本:2.5.17
源代码相关链接:https://cdn.bootcdn.net/ajax/libs/vue/2.5.17/vue.js
首先,本人采取的是最简单的debugger调试方法,按照步骤与逻辑分层次解析vue源码,我认为这是最快、最简单、也是最有效的方法。本文使用的vue调试代码如下:
<div id="main">
<h1>count: {{times}}</h1>
<p @click="cons">{{text}}</p>
</div>
<script src="./vue2.5.17/dist/vue.js"></script>
<script>
var timeId = null
var vm = new Vue({
el: '#main',
data: function () {
return {
times: 1,
text: "我的"
};
},
created: function () {
var me = this;
timeId = setInterval(function () {
me.times++;
}, 1000);
},
methods: {
cons() {
console.log(6211)
debugger
this.text = "你的"
}
}
});
这是一个非常简单的例子,但可以很好地帮助我们解读vue的整个运行流程。
render渲染篇
首先找到mount周期函数,查看options的值,其实就是我们使用new Vue
创建的实例对象
下面是template的值,即即将渲染的模板(html字符串)
然后进入创建编译函数createCompileToFunctionFn
进入createCompiler
里,得到ast的值,即一个id为main的标签,内部包含三个字节点,分别是h1标签,文本标签(换行符标签),p标签
下面是code
属性的render
值
进入mountComponent
函数
主要是调用了callHook方法,实现beforeMount(加载前的)生命周期
进入callHook
方法,调用pushTarget
和popTarget
方法,完成生命周期beforeMount(此时vm的_hasHookEvent未定义,不需要绑定事件)
下面是_hasHookEvent
方法:
这里关键的一步是给当前实例vm
,绑定更新的函数_update
,然后生成watcher
实例,实现vm
和更新组件的监视,当数据改变时,即通过watcher
来通知更新。
进入watcher
函数
之后进入到get
方法,去获取数据
在pushTarget
函数里通过Dep
绑定刚才Watcher
监视数据的变化,h1
标签中的times
数据变成了1,这时通过vm
实例绑定的_update
方法。
先调用_render
方法,得到id
为main
的节点后,调用_update
方法。
之后通过__patch__
方法,
对新旧节点进行对比
主要去研究这句代码:
createChildren(vnode, children, insertedVnodeQueue);
之后销毁旧的节点,进入
函数销毁
之后原节点销毁了,被新节点取代。
经过一系列的判断,进入parseHTML
渲染的大致流程是使用indexOf寻找开头的div标签,即”<”,最后将全部标签渲染到页面上。
总结和扩展
在新老虚拟DOM对比时:
1.首先,对比节点本身,判断是否为同一节点,如果不为相同节点,则删除该节点重新创建节点进行替换。
2.如果为相同节点,进行patchVnode,判断如何对该节点的子节点进行处理,先判断一方有子节点一方没有子节点的情况(如果新的children没有子节点,将旧的子节点移除)
3.比较如果都有子节点,则进行updateChildren,判断如何对这些新老节点的子节点进行操作。
4.匹配时,找到相同的子节点,递归比较子节点。
在diff比较中,只对同层的子节点进行比较,放弃了跨级的节点比较,加快了性能;只有当新旧children都为多个子节点时,才需要用核心的diff算法进行同层级比较。