vue 2.x内部运行机制系列文章-内部运行机制总览

1,174 阅读4分钟

全局总览

先上个图,这一篇目的主要是希望各位对vue内部运行机制有个全局的认识。之后再一一详细介绍其各自的原理。

OK,废话不多说,我们来简要介绍下各模块

初始化及mount

在执行new Vue()后,vue 会执行一个init()的方法,这里的初始化包括生命周期、事件、props、data、methods、computed、watch,以及对所有data进行observer。其中对data进行observer是我们的关键,也是vue实现响应式的关键。主要是通过给 Object.defineProperty 设置 getter 与 setter 函数,用来实现依赖收集以及数据响应式,这一部分后面会详细介绍,这里先做了解。

初始化之后会执行$mount挂载组件,若是运行时编译(存在template,不存在render function),则会进行编译,生成render函数。

compile()编译

compile阶段主要分为parse、optimize、和genarate阶段,最后生成render function

  • parse: parse的作用是通过正则匹配整个template,来生成一个叫抽象语法树(AST,简单来说就是一个json对象)的东西。
  • optimize:主要是用来标记静态节点。所谓静态节点简单理解就是页面中写死的部分(不涉及vue的内容)。标记静态节点的作用是再更新视图时,会有一个patch过程来对比新旧虚拟DOM,若是静态节点则可以直接跳过,这是vue中关于性能的一处优化。
  • genarate:主要是将AST,转换为render function字符串。

响应式

接下来就是vue的重中之重

从前面的介绍我们知道,在init()的时候,会通过Object.defineProperty()方法设置getter和setter,它使得数据在读取的时候执行getter,在修改的时候执行setter。

从上图可知,vue在执行render function的时候会指向getter对数据进行依赖收集,所谓依赖收集就是为每一个data分配一个Dep,然后将依赖于这些数据的Watcher(一般一个vue组件对应一个Watcher),添加到对应的Dep中。

在数据修改的时候会执行setter对数据进行响应式,流程是通过Dep来执行notify方法,通知对应数据的wacher执行update方法,来更新视图。

data、Dep、watcher的对应关系图如下

用比较通俗的话来讲的话,整个vue响应式机制就如同订阅记录册,册里的每一页代表一个Dep对象,里面保存了某一条数据所依赖的所有watcher,当数据改变时,就会通过Dep来通知每一个该数据的订阅者来执行update

如同订阅册的每一页记录了某本书所有订阅人,当这本书的内容有更新时,会通知所有订阅者来执行一些操作

虚拟DOM

我们知道,当我们执行编译后的render function时,会生成一个叫虚拟DOM的东西。虚拟DOM简单来说其实就是一棵以javascript对象(vNode节点)为基础的树。这棵树其实是真实DOM的映射。最终可以通过一系列的操作生成真实DOM。

一个简单的虚拟DOM如下所示

{
    tag: 'div',                 /*说明这是一个div标签*/
    children: [                 /*存放该标签的子节点*/
        {
            tag: 'a',           /*说明这是一个a标签*/
            text: '我是a标签'    /*标签的内容*/
        }
    ]
}

它和真实dom映射后就变成如下

<div>
    <a>我是a标签</a>
</div>

当然这只是一个最简单的实例,虚拟dom上还存在一些其他属性如isStatic,用来标记是否为静态节点等等。

diff算法即视图的更新

当我们每一次执行update(或首次render)后,会形成一个新的vDom.当然,做视图更新最简单的方法就是直接生成一颗真实的dom树,并用新的树代替旧的树。但其实这样做的话会造成很大程度的浪费

vue主要通过patch方法来做这方面的优化,也就是我们通常说的diff算法。patch方法会接受新旧两个虚拟DOM作为参数。进而两者进行比对,找出不同的地方,然后再和真实dom做替换,这样极大的提高了性能。当然,具体的patch方法我们在之后的文章中在介绍。

关于nextTick

通过以上的了解我们知道,vue整个内部运行的机制。那么,现在这里存在一个问题,我们每次数据更新不可能只更新一个数据或者说当某个数据持续更新时,那么是否我们每更新一次数据都会生成一个DOM呢?

答案当然是否定的。Vue.js在默认情况下,每次触发某个数据的setter方法后,对应的 Watcher 对象会被 push 进一个队列中,它会在下一次tick的时候将这些watcher拿出来,执行一遍对应的patch操作。

那么什么是下一个tick呢?且听随后分解。