Vue源码,你真的看懂了吗(四)__Vue.js
发布于 3 年前 作者 banyungong 1285 次浏览 来自 分享
粉丝福利 : 关注VUE中文社区公众号,回复视频领取粉丝福利

模版编译原理

在前端开发中,我们通过写HTML代码来实现页面的结构,我们称这些HTML代码为模板。在vue中,模板先通过模板编译转换成渲染函数,当内部状态发生变化的时候,Vue.js会结合响应式系统,找出最小数量的组件进行重新渲染以进行最少量的DOM操作。

Vue中创建HTML除了使用模版,也可以手动写渲染函数来创建HTML,还可以使用JSX创建HTML。

流程

从这张图中,我们可以看到Vue的模版编译是在mount的过程中进行的,在mount的时候执行了compile这个方法来将template里的内容转换成真正的HTML代码,接下来就到了监听绑定的数据重新生成vnode,通过新旧vnode比对更新用户界面。

compile方法分为三个步骤:

  • parse函数解析template
  • optimize函数优化静态内容
  • generate函数创建render函数字符串

也就是说,编译原理的整体逻辑分为下面三个部分。

  • 将 模板字符串 转换成 element AST(解析器)
  • 对 AST 进行静态节点标记,主要用来做虚拟DOM的渲染优化(优化器)
  • 使用 AST 生成 render 函数代码字符串(代码生成器)

解析器

作用:通过 模版 得到 AST(抽象语法树)

例如:

<div>
  <p>{{name}}</p>
</div>

上面的代码转换成AST后的样子如下:

{
  tag: "div"
  type: 1,
  staticRoot: false,
  static: false,
  plain: true,
  parent: undefined,
  attrsList: [],
  attrsMap: {},
  children: [
    {
      tag: "p"
      type: 1,
      staticRoot: false,
      static: false,
      plain: true,
      parent: {tag: "div", ...},
      attrsList: [],
      attrsMap: {},
      children: [{
        type: 2,
        text: "{{name}}",
        static: false,
        expression: "_s(name)"
      }]
    }
  ]
}

它在被HTML解析器解析的过程如下。

第一轮循环时,截取出一段字符串

,并且触发钩子函数start,截取后的结果为:

`
  <p>{{name}}</p>
</div>`

第二轮循环时,截取出一段字符串:

`
  `

并且触发钩子函数chars,截取后的结果为:

`<p>{{name}}</p>
</div>`

第三轮循环时,截取出一段字符串

,并且触发钩子函数start,截取后的结果为:

`{{name}}</p>
</div>`

第四轮循环时,截取出一段字符串{{name}},并且触发钩子函数chars,截取后的结果为:

`</p>
</div>`

第五轮循环时,截取出一段字符串

,并且触发钩子函数end,截取后的结果为:

`
</div>`

第六轮循环时,截取出一段字符串:

`
`

并且触发钩子函数chars,截取后的结果为:

`</div>`

第七轮循环时,截取出一段字符串

,并且触发钩子函数end,截取后的结果为:

``

解析完毕。

HTML解析器内部原理:一小段一小段截取模版字符串,根据每次截取的一小段字符串触发不同的钩子函数,直到模版字符串截空通知运行。

使用栈维护DOM层级

构建AST层级关系只需要维护一个栈即可,这个层级关系也可以理解为DOM的深度。

使用栈结构还有另外一个作用:检测出HTML标签是否正确闭合。

优化器

作用:在AST中找出静态子树并打上标记。

好处:

  • 重新渲染时,不需要为静态子树创建新节点
  • 在虚拟DOM中打补丁的过程可以被跳过

内部实现的两个步骤:

  • 在AST中找出所有静态节点并打上标记
  • 在AST中找出所有静态根节点并打上标记

通过递归的方式从上向下标记静态节点。标记完静态节点之后需要标记静态根节点,使用递归的方式从上向下寻找,找到的第一个静态节点就是静态根节点,同时不会再向下继续寻找。

静态节点是永远不会发生变化的DOM节点,这个节点不需要重新渲染,所以标记起来不像普通节点一样创建更新它而直接克隆它,可以避免一些无用功来提升性能。其子节点必须是静态节点。

两种特殊情况:

  • 一个静态根节点的子节点只有一个文本节点
  • 找到的静态根节点是一个没有子节点的静态节点

这两种情况不会将这个节点标记为静态根节点,因为优化成本大于收益。

代码生成器

作用:使用 AST 生成 render 函数代码字符串。

本质是字符串拼接的过程。

原理:通过递归AST来生成字符串,最先生成根节点,然后在子节点字符串生成后,将其拼接在根节点的参数中,子节点的自己点拼接在子节点的参数中,这样一层一层地拼接,直到最后拼接成完整的字符串。

不同类型的节点生成字符串的方式不同,有三种类型:

  • 元素节点
  • 文本节点
  • 注释节点

字符串拼接好之后,会将字符串拼在with中返回给调用者。

假设我们有这么一段 template

<template>
  <div id="test">
    {{val}}
    <img src="http://xx.jpg">
  </div>
</template>

最终会被转换成这样子的函数字符串

{render: "with(this){return _c('div',{attrs:{"id":"test"}},[[_v(_s(val))]),_v(" "),_m(0)])}"}

版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 作者: 蒲月阿七 原文链接:https://juejin.im/post/6933967729841930253

回到顶部