Wang's blog Wang's blog
首页
  • 前端文章

    • HTML教程
    • CSS
    • JavaScript
  • 前端框架

    • Vue
    • React
    • VuePress
    • Electron
  • 后端技术

    • Npm
    • Node
    • TypeScript
  • 编程规范

    • 规范
  • 我的笔记
  • Git
  • GitHub
  • VSCode
  • Mac工具
  • 数据库
  • Google
  • 服务器
  • Python爬虫
  • 前端教程
更多
收藏
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Wang Mings

跟随大神,成为大神!
首页
  • 前端文章

    • HTML教程
    • CSS
    • JavaScript
  • 前端框架

    • Vue
    • React
    • VuePress
    • Electron
  • 后端技术

    • Npm
    • Node
    • TypeScript
  • 编程规范

    • 规范
  • 我的笔记
  • Git
  • GitHub
  • VSCode
  • Mac工具
  • 数据库
  • Google
  • 服务器
  • Python爬虫
  • 前端教程
更多
收藏
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Python爬虫

  • 前端教程

    • 团队规范

    • Project

    • JS

    • NodeJS

    • Vue

      • 个人理解Vue和React区别
      • Vue高级用法
      • Vue2.x源码分析 - 框架结构
      • Vue2.x源码分析 - 模版编译以及挂载
        • [#](#api层) API层
        • [#](#源码层) 源码层
          • [#](#_1-解析template模板-得到render函数并绑定到vue选项上) 1. 解析template模板,得到render函数并绑定到Vue选项上
          • [#](#_2-挂载渲染-mount-call-this-el) 2. 挂载渲染. mount.call(this, el)
        • [#](#完整流程) 完整流程
      • 虚拟dom算法库 - snabbdom
      • Vue2.x源码分析 - Virtual DOM实现
      • Vue2.x源码分析 - 事件系统
      • Vue2.x源码分析 - 组件系统
      • Vue2.x源码分析 - Vue.nextTick
      • Vue2.x源码分析 - inject provide
      • Vue2.x源码分析 - 解析Template模板
      • Vue2.x源码分析 - 响应式原理
      • Vue2.x源码分析 - v-model
      • Vue CLI3 插件系统原理
      • Vue Loader v15 源码解析
      • Vue3 设计思想
      • Vue3 RFCS导读
      • Vue3 响应式原理 - Ref Reactive Effect源码分析
      • Vue3 API 源码解析
      • 为何Vue3 Proxy 更快
      • Vue核心原理 - 笔记
    • React

    • 效率工具

    • 读书笔记

  • 教程
  • 前端教程
  • Vue
wangmings
2022-07-19
目录

Vue2.x源码分析 - 模版编译以及挂载

# # Vue2.x源码分析 - 模版编译以及挂载

由于2.x版本中引入虚拟DOM的缘故,Vue支持模板和手写render两种方式(也支持jsx,属于第三方插件帮你转换为render函数;当然模板方式最终也是转换为render函数,但属于官方内置编译器),官方推荐模板这种写法,降低使用门槛。

Vue是如何解析模板并挂载到真实的DOM上呢?

# # API层

先看下Vue API层是如何使用的:

    // 使用Vue.prototype.$mount(el)或者放在options.el里
    new Vue({template, data}).$mount(el)
    // or
    new Vue({ el, template, data})
    
1
2
3
4
5

# # 源码层

先了解下源码入口区别:

  • platforms/web/entry-runtime.js
    • Vue运行时,简单说就是对外只导出Vue这个类,因为所有Vue的配置以及设置,都是挂载到Vue的原型上。
  • platforms/web/entry-compiler.js
    • Vue内置编译器,主要是暴露一些函数出来。主要有compile函数,把template字符串转换为render函数。
  • platforms/entry-runtime-with-compiler.js
    • 带编译器的运行时,最常使用的Vue依赖包。可通过new Vue({}).$mount(el)直接挂载DOM。

我们源码分析的是带编译器的运行时,以下看下笔者简化的源码流程,注意高亮的关键代码:

# # 1. 解析template模板,得到render函数并绑定到Vue选项上

  • const { render } = compileToFunctions(options.template, ...)
    • const { compileToFunctions } = createCompiler(baseOptions) 'src/platforms/web/compiler/index.js'
      • const createCompiler = createCompilerCreator(baseCompile(template, options)) 'src/compiler/index.js' 1. ast = parse(template.trim(), options) 解析模板字符串生成 AST 'src/compiler/parser/index.js'

                    * parse 的过程是利用正则表达式顺序解析模板,当解析到开始标签、闭合标签、文本的时候都会分别执行对应的回调函数,来达到构造 AST 树的目的。
                    * type 为 1 表示是普通元素,为 2 表示是表达式,为 3 表示是纯文本
        
              // AST数据结构
              // 最终生成的是一颗AST树结构
              declare type ASTElement = {
                  type: 1;
                  tag: string;
                  attrsList: Array<{ name: string; value: any }>;
                  attrsMap: { [key: string]: any };
                  parent: ASTElement | void;
                  children: Array<ASTNode>; // ASTNode = ASTElement | ASTText | ASTExpression;
              
                  processed?: true;
              
                  static?: boolean;
                  staticRoot?: boolean;
                  staticInFor?: boolean;
                  staticProcessed?: boolean;
                  hasBindings?: boolean;
              
                  text?: string;
                  attrs?: Array<{ name: string; value: any }>;
                  props?: Array<{ name: string; value: string }>;
                  plain?: boolean;
                  pre?: true;
                  ns?: string;
              
                  component?: string;
                  inlineTemplate?: true;
                  transitionMode?: string | null;
                  slotName?: ?string;
                  slotTarget?: ?string;
                  slotScope?: ?string;
                  scopedSlots?: { [name: string]: ASTElement };
              
                  ref?: string;
                  refInFor?: boolean;
              
                  if?: string;
                  ifProcessed?: boolean;
                  elseif?: string;
                  else?: true;
                  ifConditions?: ASTIfConditions;
              
                  for?: string;
                  forProcessed?: boolean;
                  key?: string;
                  alias?: string;
                  iterator1?: string;
                  iterator2?: string;
              
                  staticClass?: string;
                  classBinding?: string;
                  staticStyle?: string;
                  styleBinding?: string;
                  events?: ASTElementHandlers;
                  nativeEvents?: ASTElementHandlers;
              
                  transition?: string | true;
                  transitionOnAppear?: boolean;
              
                  model?: {
                      value: string;
                      callback: string;
                      expression: string;
                  };
              
                  directives?: Array<ASTDirective>;
              
                  forbidden?: true;
                  once?: true;
                  onceProcessed?: boolean;
                  wrapData?: (code: string) => string;
                  wrapListeners?: (code: string) => string;
              
                  // 2.4 ssr optimization
                  ssrOptimizability?: number;
              
                  // weex specific
                  appendAsTree?: boolean;
                  };
              
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
            2.  `optimize(ast, options)` 优化语法树 'src/compiler/optimizer.js'
                  * 很多数据是首次渲染后就永远不会变化的,那么这部分数据生成的 DOM 也不会变化,我们可以在 patch 的过程跳过对他们的比对。
                  * 只干两件事:1. 标记静态节点 2. 标记静态根(静态根一定是静态节点)
            3.  `code = generate(ast, options)` 生成代码\(code对象包含render函数\) 'src/compiler/codegen/index.js'

                  * 把优化后的 AST 树转换成可执行的代码。可执行代码最终获得VNode(虚拟DOM的基础)
              return (isShow) ?
                  _c('ul', {
                      staticClass: "list",
                      class: bindCls
                  },
                  _l((data), function(item, index) {
                      return _c('li', {
                      on: {
                          "click": function($event) {
                          clickItem(index)
                          }
                      }
                      },
                      [_v(_s(item) + ":" + _s(index))])
                  })
                  ) : _e()
              
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# # 2. 挂载渲染. mount.call(this, el)

  • Vue.prototype.$mount 'platforms/web/runtime/index.js'
    • mountComponent 'core/instance/lifecycle.js'
      • new Watch(vm, updateComponent) 1. vm._render() 'src/core/instance/render.js'

                    * 执行render函数,拿到VNode\(注意render 函数的参数h为vm.\$createElement\)
        
              const { render } = vm.$options
              return vnode = render.call(vm._renderProxy, vm.$createElement)
              
1
2
3
            2.  `vm._update(VNode)` 'src/core/instance/lifecycle.js'

                  * 更新DOM, `vm.__patch__(preVNode, VNode)` 'src/platforms/web/runtime/patch.js'
                        * `createPatchFunction` --> `patch(oldVNode, vnode)` 'src/core/vdom/patch.js',详见[Virtual DOM实现](/blogs/vue/vue-code.3.vdom.html)
                              * diff等,真正的操作更新DOM
                              * 绑定class、style、event等
              const prevVnode = vm._vnode
              if (!prevVnode) {
                  // initial render
                  vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
              } else {
                  // updates
                  vm.$el = vm.__patch__(prevVnode, vnode)
              }
              
1
2
3
4
5
6
7
8
9

# # 完整流程

以下是vue挂载的整个流程,其实就是vnode生成真实dom的规则。其中还包括vue自定义组件的构建流程,详细说明可看Vue2.x源码分析 - 组件系统章节。

image

编辑 (opens new window)
Vue2.x源码分析 - 框架结构
虚拟dom算法库 - snabbdom

← Vue2.x源码分析 - 框架结构 虚拟dom算法库 - snabbdom→

最近更新
01
theme-vdoing-blog博客静态编译问题
09-16
02
搜索引擎
07-19
03
友情链接
07-19
更多文章>
Theme by Vdoing | Copyright © 2019-2022 Evan Xu | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式