一、前言

Vue.js框架是目前比较火的MVVM框架之一,简单易上手的学习曲线,友好的官方文档,配套的构建工具,让Vue.js在2016大放异彩,大有赶超React之势。前不久Vue.js 2.0正式版已出,在体积优化(相比1.0减少了50%)、性能提升(相比1.0提升60%)、API优化等各方面都更上一层楼。

本文是系列文章,主要想通过对于Vue.js 2.0源码的分析,从代码层面解析Vue.js的实现原理,帮助读者能够更深入地理解整个框架的思想。此篇文章主要介绍前端渲染部分。

不足之处还请批评指正,欢迎一起交流学习。

二、Vue的初始化

我们在使用Vue.js的时候,最基本的一个使用,就是在HTML引入Vue.js的库文件,并写如下一段代码:

1.var app = new Vue({
2. el: '#app',
3. data: {
4. message: 'Hello Vue!'
5. }
6.})

new Vue,本质就是生成一个Vue的对象,我们来了解一下这个生成Vue对象的过程是怎样的:

首先,Vue的入口是/src/entries/web-runtime-with-compiler.js,这是由config.js配置文件决定的。

这个入口文件中import了很多文件,其中有一条主要的脉络:

/src/entries/web-runtime-with-compiler.js
引用了/src/entries/web-runtime.js
引用了/src/core/index.js
引用了/src/core/instance/index.js

其中/src/core/instance/index.js是最核心的初始化代码,其中:

红框部分,就是整个Vue的类的核心方法。其含义给读者解读一下:

1.//初始化的入口,各种初始化工作
2.initMixin(Vue)
3.//数据绑定的核心方法,包括常用的$watch方法
4.stateMixin(Vue)
5.//事件的核心方法,包括常用的$on,$off,$emit方法
6.eventsMixin(Vue)
7.//生命周期的核心方法
8.lifecycleMixin(Vue)
9.//渲染的核心方法,用来生成render函数以及VNode
10.renderMixin(Vue)

其中new Vue就是执行下面的这个函数:

_init方法就是initMixin中的_init方法。

至此,程序沿着这个_init方法继续走下去。

三、Vue的渲染逻辑——Render函数

在定义完成Vue对象的初始化工作之后,本文主要是讲渲染部分,那么我们接上面的逻辑,看Vue.js是如何渲染页面的。在上图中我们看到有一个initRender的方法:

在该方法中会执行红框部分的内容:

$mount方法就是整个渲染过程的起始点。具体定义是在/src/entries/web-runtime-with-compiler.js中,根据代码整理成流程图:

由此图可以看到,在渲染过程中,提供了三种渲染模式,自定义Render函数、template、el均可以渲染页面,也就是对应我们使用Vue时,三种写法:

1. 自定义Render函数

1.Vue.component('anchored-heading', {
2. render: function (createElement) {
3. return createElement(
4. 'h' + this.level, // tag name 标签名称
5. this.$slots.default // 子组件中的阵列
6. )
7. },
8. props: {
9. level: {
10. type: Number,
11. required: true
12. }
13. }
14.})

2.template写法

1.var vm = new Vue({
2. data: {
3. // 以一个空值声明 `msg`
4. msg: ''
5. },
6. template: '<div>{{msg}}</div>'
7.})

3.el写法(这个就是入门时最基本的写法)

1.var app = new Vue({
2. el: '#app',
3. data: {
4. message: 'Hello Vue!'
5. }
6.})

这三种渲染模式最终都是要得到Render函数。只不过用户自定义的Render函数省去了程序分析的过程,等同于处理过的Render函数,而普通的template或者el只是字符串,需要解析成AST,再将AST转化为Render函数。

记住一点,无论哪种方法,都要得到Render函数。

我们在使用过程中具体要使用哪种调用方式,要根据具体的需求来。

  • 如果是比较简单的逻辑,使用template和el比较好,因为这两种都属于声明式渲染,对用户理解比较容易,但灵活性比较差,因为最终生成的Render函数是由程序通过AST解析优化得到的;

  • 而使用自定义Render函数相当于人已经将逻辑翻译给程序,能够胜任复杂的逻辑,灵活性高,但对于用户的理解相对差点。

四、Vue的渲染逻辑——VNode对象&patch方法

根据上面的结论,我们无论怎么渲染,最终会得到Render函数,而Render函数的作用是什么呢?我们看到在/src/core/instance/lifecycle.js中有这么一段代码:

1.vm._watcher = new Watcher(vm, () => {
2. vm._update(vm._render(), hydrating)
3.}, noop);

意思就是,通过Watcher的绑定,每当数据发生变化时,执行_update的方法,此时会先执行vm._render(),在这个vm._render()中,我们的Render函数会执行,而得到VNode对象。

VNode对象是什么?VNode就是Vue.js 2.0中的Virtual DOM,在Vue.js 2.0中,相较Vue.js 1.0引入了Virtual DOM的概念,这也是Vue.js 2.0性能提升的一大关键。Virtual DOM有多种实现方式,但基本思路都是一样的,分为两步:

1. Javascript模拟DOM模型树

在Vue.js 2.0中Javascript模拟DOM模型树就是VNode,Render函数执行后都会返回VNode对象,为下一步操作做准备。在/src/core/vdom/vnode.js中,我们可以看到VNode的具体数据结构:

VNode的数据结构中还有VNodeData、VNodeDirective、VNodeComponentOptions,这些数据结构都是对DOM节点的一些描述,本文不一一介绍。读者可以根据源码来理解这些数据结构。(PS:Vue.js使用了flow,标识了参数的静态类型,对理解代码很有帮助^_^)

2. DOM模型树通过DOM Diff算法查找差异,将差异转为真正DOM节点

我们知道Render函数执行生成了VNode,而VNode只是Virtual DOM,我们还需要通过DOM Diff之后,来生成真正的DOM节点。在Vue.js 2.0中,是通过/src/core/vdom/patch.js中的patch(oldVnode, vnode ,hydrating)方法来完成的。

该方法有三个参数oldVnode表示旧VNode,vnode表示新VNode,hydrating表示是否直接使用服务端渲染的DOM元素,这个本文不作讨论,在服务端渲染篇再详细介绍。

其主要逻辑为当VNode为真实元素或旧的VNode和新的VNode完全相同时,直接调用createElm方法生成真实的DOM树,当VNode新旧存在差异时,则调用patchVnode方法,通过比较新旧VNode节点,根据不同的状态对DOM做合理的添加、删除、修改DOM(这里的Diff算法有兴趣的读者可以自行阅读patchVnode方法,鉴于篇幅不再赘述),再调用createElm生成真实的DOM树。

五、Vue的渲染小结

回过头来看,这里的渲染逻辑并不是特别复杂,核心关键的几步流程还是非常清晰的:

  1. new Vue,执行初始化
  2. 挂载$mount方法,通过自定义Render方法、template、el等生成Render函数
  3. 通过Watcher监听数据的变化
  4. 当数据发生变化时,Render函数执行生成VNode对象
  5. 通过patch方法,对比新旧VNode对象,通过DOM Diff算法,添加、修改、删除真正的DOM元素

至此,整个new Vue的渲染过程完毕。

Vue.js 2.0源码解析之前端渲染篇的更多相关文章

  1. solr&lucene3.6.0源码解析(四)

    本文要描述的是solr的查询插件,该查询插件目的用于生成Lucene的查询Query,类似于查询条件表达式,与solr查询插件相关UML类图如下: 如果我们强行将上面的类图纳入某种设计模式语言的话,本 ...

  2. solr&lucene3.6.0源码解析(三)

    solr索引操作(包括新增 更新 删除 提交 合并等)相关UML图如下 从上面的类图我们可以发现,其中体现了工厂方法模式及责任链模式的运用 UpdateRequestProcessor相当于责任链模式 ...

  3. Heritrix 3.1.0 源码解析(三十七)

    今天有兴趣重新看了一下heritrix3.1.0系统里面的线程池源码,heritrix系统没有采用java的cocurrency包里面的并发框架,而是采用了线程组ThreadGroup类来实现线程池的 ...

  4. PureMVC(JS版)源码解析:总结

    PureMVC源码中设计到的11个类已经全部解析完了,回首想想,花了一周的时间做的这点事情还是挺值得的,自己的文字组织表达能力和对pureMVC的理解也在写博客的过程中得到了些提升.我也是第一次写系列 ...

  5. PureMVC(JS版)源码解析

    PureMVC(JS版)源码解析:总结   PureMVC源码中设计到的11个类已经全部解析完了,回首想想,花了一周的时间做的这点事情还是挺值得的,自己的文字组织表达能力和对pureMVC的理解也在写 ...

  6. Android事件总线(二)EventBus3.0源码解析

    1.构造函数 当我们要调用EventBus的功能时,比如注册或者发送事件,总会调用EventBus.getDefault()来获取EventBus实例: public static EventBus ...

  7. solr&lucene3.6.0源码解析(二)

    上文描述了solr3.6.0怎么采用maven管理的方式在eclipse中搭建开发环境,在solr中,为了提高搜索性能,采用了缓存机制,这里描述的是LRU缓存,这里用到了 LinkedHashMap类 ...

  8. solr&lucene3.6.0源码解析(一)

      本文作为系列的第一篇,主要描述的是solr3.6.0开发环境的搭建   首先我们需要从官方网站下载solr的相关文件,下载地址为http://archive.apache.org/dist/luc ...

  9. apache mina2.0源码解析(一)

    apache mina是一个基于java nio的网络通信框架,为TCP UDP ARP等协议提供了一致的编程模型:其源码结构展示了优秀的设计案例,可以为我们的编程事业提供参考. 依照惯例,首先搭建a ...

随机推荐

  1. python基础--用python执行系统命令

    from os import system print(system('ping www.baidu.com'))

  2. InputStream 、 InputStreamReader 、 BufferedReader

    1.InputStream.OutputStream 处理字节流的抽象类 InputStream 是字节输入流的所有类的超类,一般我们使用它的子类,如FileInputStream等. OutputS ...

  3. POJ 3041 Asteroids | 匈牙利算法模板

    emmmmm 让你敲个匈牙利 #include<cstdio> #include<algorithm> #include<cstring> #define N 51 ...

  4. Hash表模板

    namespace Hash { ; ; struct adj { ll nxt,v,num,val; }e[N]; ll head[H],ecnt=; void init() { ecnt=; me ...

  5. 自动设置 rem es模块写法

    export default function () { let html = document.documentElement; function onWindowResize() { if (ht ...

  6. 汕头市队赛SRM15

    T1——czl SRM 15 众所周知,czl家养了一只可♂爱的***(已屏蔽),那只东西很贪吃,所以czl家很多零食仓库,然而这些仓库里有很多老鼠. 为了心爱的***,czl决定点燃纯艾条,用烟熏老 ...

  7. [ CodeVS冲杯之路 ] P1116

    不充钱,你怎么AC? 题目:http://codevs.cn/problem/1116/ 数据很小,DFS可A,每层枚举颜色,判断相邻的点是否有重复的颜色,记得回溯时把颜色染回0,即无颜色 这里我使用 ...

  8. [ CodeVS冲杯之路 ] P3143

     不充钱,你怎么AC? 题目:http://codevs.cn/problem/3143/ 大水题一道,只要会遍历,这里讲一下思路 先序遍历:先输出,然后左儿子,最后右儿子 中序遍历:先左儿子,再输出 ...

  9. Linux Mint---shutter截图软件

    shutter 可以说是linux下最好的截图软件了,默认安装好后不能编辑截图,解决方法如下: 1-关闭shutter 2-sudo apt-get install shutter libgoo-ca ...

  10. mysql 连接失败问题汇集

    FHost '192.168.5.128' is not allowed to connect to this MySQL serverConnection closed by foreign hos ...