前言
vue执行过程内部都发生了什么,什么是模板解析,为什么能做到响应式更新视图?以下为大家介绍下一个vue项目执行的一个大致流程。(这里讲的是一个大致流程,vue真实的实现比这里说的复杂多,但本文主要是为你介绍大致的实现思路,加深你对vue的理解)
首先我们来看下这个流程的总体步骤:
然后我们根据这四个步骤来一 一介绍
一、 模板解析为 render 函数
首先,什么是模板?模板就是那些html标签,包括嵌入到html标签里面的指令和变量 v-if=“isShow”。在vue看来它们都是一堆字符串,需要先解析它们,并由此生成一个 render函数,render函数就是用来创建虚拟DOM的(vdom),举个栗子
假如模板为
<div id="app">
<p> {{ price }} </p>
</div>
那么生成的render函数内容为
这里面的this指向的是vue实例,使用with
是方便书写,里面需要用到data
的属性时可以直接写变量名,不需要类似这样:vm.price;
_c( )
函数返回的是一个 vnode(虚拟节点,也就是描述DOM的js对象,对虚拟DOM不了解的建议先去了解下),第一个参数是标签名,第二是属性(没有属性可省略),第三是子节点数组;
_v( )
函数返回的是一个描述文本节点的虚拟节点;
_s( )
函数类似于toString返回一个字符串。
由于这个render函数内部是由一个 _c( )
函数包裹着,所以,render函数执行结果返回的是一个 虚拟节点 vnode。注意这一步只是生成了render函数,并没有调用!!而且解析模板这一步在使用打包工具的时候,就已经由打包工具生成好了render函数。然后进入下一步
记住,包括v-if,v-for等等这些逻辑处理都已经存在于render中了,而这些指令最终有什么效果都取决于赋给它的变量,所以最终还是data中的数据起着关键作用,所以说vue是数据驱动着视图,我们只需要关心数据层
这是这一步的流程
二、 响应开始监听
当程序开始执行的时候,我们知道会先实例一个Vue对象,
var vm = new Vue({ options });
options里面包含了data属性,我们要用到的属性值也都是写在这个data里面。此时vue需要将我们在data里面声明的属性绑定到自己身上,也就是vm身上。
这里就用到了关键函数:Object.defineProperty( obj, name, {...} );
这个函数可以为一个对象添加属性,并可以在这个属性被访问或被赋值时执行我们添加的逻辑代码。了解了这个函数之后,你就大致知道为什么我们修改data的中的属性,视图也会跟着修改了。
我们来简单模拟下vue把data中属性通过defineProperty
添加到自己身上
class Vue {
constructor(options){
this.$el = options.el;
// 模拟 vue内部绑定属性
let data = options.data();
for (const key in data) {
if (data.hasOwnProperty(key)) {
Object.defineProperty(this, key, {
get(){
console.log('get:监听处理逻辑代码');
return data[key];
},
set(newValue){
data[key] = newValue;
console.log('set:调用更新模板视图的函数,完成响应式');
}
})
}
}
}
}
//实例化
var vm = new Vue({
el: '#app',
data () {
return {
price: 100
}
}
})
执行完这一步后,vue实例就拥有了data中的属性,我们可以直接用 vm.price
访问到,并且访问或者赋值这些属性时都会执行我们添加的逻辑代码,至于vue在里面添加了什么代码继续往下看
执行完这一步,我们在第一步生成的render函数才可以执行,还记得吗,上面的render函数中引用了price变量,而且是通过 vm.price访问的。所以只有执行完第二步,vm上面才有price这个变量
三 、渲染页面,绑定依赖
执行完前两步,我们已经有了render函数和属性,开始来渲染页面了。(如果对 vdom-虚拟DOM不太了解的话,可以先去看下snabbdom,了解下渲染vdom的几个关键函数,因为这里vue内部处理vdom与snabbdom核心相似)
首先,vue中有一个函数,专门来管理渲染页面,叫updateComponent();
,updateComponent
函数类似于这样:
其中 vm._render( )
就是我们前面获得的render函数,执行返回的是一个vnode,把vnode传入vm._update( )
中去,并利用关键函数 __patch__( )
对虚拟DOM生成真正的DOM,渲染到页面(同样,如果你了解snabbdom,那么这里你很容易就能看懂。)
这里vm._update( )
有两种情况:
第一种是首次渲染,就直接将vnode一次性渲染到vm.$el
根节点里面去;
第二种就不是第一次渲染已经有视图了,而是需要更新视图。然后传入 __patch__( )
函数的是两个vnode,第一个是旧的vnode,第二个是render函数重新执行返回的最新vnode,然后__patch__( )
会同过 diff 算法比较出两份vnode的差异,然后仅修改那些差异来更新视图,大大提升了性能。
具体流程
这个时候页面已经显示出来了,接下来就是显示实时监听了
四、 监听data属性变化
vue是由数据驱动视图更新的,而这里的数据就是data里面的属性。
例如,当我们通过点击按钮修改其中一个属性的值时,就会触发该属性的 set函数,(还记得在第二步我们为vm绑定属性时,为每个属性设置了get
,set
方法吗),为了做到视图响应式更新,那么我们只要在set
函数里面加入更新视图的函数 updateComponent ( )
就可以了(vue的具体实现会比这复杂多,但我们这里主要为了学习它的思路)。
那么按照我们所说的,在set
里面加入updateComponent ( )
,那么属性被修改时,触发updateComponent ( )
,vm.render( )
重新执行,获得最新的vnode,传入vm._update( )
,通过__patch__( )
函数比较新老vnode的差异,并更新视图,这就完成了响应式。
到了这里基本就是vue实现的大致思路,但是你可能有个疑惑?监听属性变化并响应式更新到视图去好像只需要监听set
就行,没有关get
啥事哩?
那我们可不可以只监听set
就行?
答案当然是不可以咯,这里有些细节没介绍到。就是我们的data中有很多属性,可能有些属性我们在程序中根本就没用到,而有用到的属性都会触发get
方法,所以没有触发过get
方法的属性,那我们也无需要关心它的set
。这样,就算改变它的值,也不会调用更新视图的函数 updateComponent ( )
,所以就避免了不必要的重复渲染。
下面关于这个疑惑给张图:
希望看完的你能对vue的实现有一个大致的认识