深入Vue2.0底层思想–模板渲染

初衷

原州ssl适用于网站、小程序/APP、API接口等需要进行数据传输应用场景,ssl证书未来市场广阔!成为创新互联公司的ssl证书销售渠道,可以享受市场价格4-6折优惠!如果有意向欢迎电话联系或者加微信:13518219792(备注:SSL证书合作)期待与您的合作!

在使用vue2.0的过程,有时看API很难理解vue作者的思想,这促使我想要去深入了解vue底层的思想,了解完底层的一些思想,才能更好的用活框架,虽然网上已经有很多源码解析的文档,但我觉得只有自己动手了,才能更加深印象。

vue2.0和1.0模板渲染的区别

Vue 2.0 中模板渲染与 Vue 1.0 完全不同,1.0 中采用的 DocumentFragment (想了解可以观看这篇文章),而 2.0 中借鉴 React 的 Virtual DOM。基于 Virtual DOM,2.0 还可以支持服务端渲染(SSR),也支持 JSX 语法。

知识普及

在开始阅读源码之前,先了解一些相关的知识:AST 数据结构,VNode 数据结构,createElement 的问题,render函数。

AST 数据结构

AST 的全称是 Abstract Syntax Tree(抽象语法树),是源代码的抽象语法结构的树状表现形式,计算机学科中编译原理的概念。而vue就是将模板代码映射为AST数据结构,进行语法解析。

我们看一下 Vue 2.0 源码中 AST 数据结构 的定义:

 
 
 
 
  1. declare type ASTNode = ASTElement | ASTText | ASTExpression
  2. declare type ASTElement = { // 有关元素的一些定义
  3.   type: 1;
  4.   tag: string;
  5.   attrsList: Array{ name: string; value: string }>;
  6.   attrsMap: { [key: string]: string | null };
  7.   parent: ASTElement | void;
  8.   children: ArrayASTNode>;
  9.   //......
  10. }
  11. declare type ASTExpression = {
  12.   type: 2;
  13.   expression: string;
  14.   text: string;
  15.   static?: boolean;
  16. }
  17. declare type ASTText = {
  18.   type: 3;
  19.   text: string;
  20.   static?: boolean;

我们看到 ASTNode 有三种形式:ASTElement,ASTText,ASTExpression。用属性 type 区分。

VNode数据结构

下面是 Vue 2.0 源码中 VNode 数据结构 的定义 (带注释的跟下面介绍的内容有关):

 
 
 
 
  1. constructor {
  2.   this.tag = tag   //元素标签
  3.   this.data = data  //属性
  4.   this.children = children  //子元素列表
  5.   this.text = text
  6.   this.elm = elm  //对应的真实 DOM 元素
  7.   this.ns = undefined
  8.   this.context = context
  9.   this.functionalContext = undefined
  10.   this.key = data && data.key
  11.   this.componentOptions = componentOptions
  12.   this.componentInstance = undefined
  13.   this.parent = undefined
  14.   this.raw = false
  15.   this.isStatic = false //是否被标记为静态节点
  16.   this.isRootInsert = true
  17.   this.isComment = false
  18.   this.isCloned = false
  19.   this.isOnce = false

真实DOM存在什么问题,为什么要用虚拟DOM

我们为什么不直接使用原生 DOM 元素,而是使用真实 DOM 元素的简化版 VNode,最大的原因就是 document.createElement 这个方法创建的真实 DOM 元素会带来性能上的损失。我们来看一个 document.createElement 方法的例子

 
 
 
 
  1. let div = document.createElement('div');
  2. for(let k in div) {
  3.   console.log(k);

打开 console 运行一下上面的代码,会发现打印出来的属性多达 228 个,而这些属性有 90% 多对我们来说都是无用的。VNode 就是简化版的真实 DOM 元素,关联着真实的dom,比如属性elm,只包括我们需要的属性,并新增了一些在 diff 过程中需要使用的属性,例如 isStatic。

render函数

这个函数是通过编译模板文件得到的,其运行结果是 VNode。render 函数 与 JSX 类似,Vue 2.0 中除了 Template 也支持 JSX 的写法。大家可以使用 Vue.compile(template)方法编译下面这段模板。

 
 
 
 
  1. div id="app">
  2.   header>
  3.     h1>I am a template!/h1>
  4.   /header>
  5.   p v-if="message">
  6.     {{ message }}
  7.   /p>
  8.   p v-else>
  9.     No message.
  10.   /p>
  11. /div> 

方法会返回一个对象,对象中有 render 和 staticRenderFns 两个值。看一下生成的 render函数

 
 
 
 
  1. (function() {
  2.   with(this){
  3.     return _c('div',{   //创建一个 div 元素
  4.       attrs:{"id":"app"}  //div 添加属性 id
  5.       },[
  6.         _m(0),  //静态节点 header,此处对应 staticRenderFns 数组索引为 0 的 render 函数
  7.         _v(" "), //空的文本节点
  8.         (message) //三元表达式,判断 message 是否存在
  9.          //如果存在,创建 p 元素,元素里面有文本,值为 toString(message)
  10.         ?_c('p',[_v("\n    "+_s(message)+"\n  ")])
  11.         //如果不存在,创建 p 元素,元素里面有文本,值为 No message.
  12.         :_c('p',[_v("\n    No message.\n  ")])
  13.       ]
  14.     )
  15.   }
  16. }) 

要看懂上面的 render函数,只需要了解 _c,_m,_v,_s 这几个函数的定义,其中 _c 是 createElement(创建元素),_m 是 renderStatic(渲染静态节点),_v 是 createTextVNode(创建文本dom),_s 是 toString (转换为字符串)

除了 render 函数,还有一个 staticRenderFns 数组,这个数组中的函数与 VDOM 中的 diff 算法优化相关,我们会在编译阶段给后面不会发生变化的 VNode 节点打上 static 为 true 的标签,那些被标记为静态节点的 VNode 就会单独生成 staticRenderFns 函数

 
 
 
 
  1. (function() { //上面 render 函数 中的 _m(0) 会调用这个方法
  2.   with(this){
  3.     return _c('header',[_c('h1',[_v("I'm a template!")])])
  4.   }
  5. }) 

模板渲染过程(重要的函数介绍)

了解完一些基础知识后,接下来我们讲解下模板的渲染过程

$mount 函数,主要是获取 template,然后进入 compileToFunctions 函数。

compileToFunctions 函数,主要将 template 编译成 render 函数。首先读缓存,没有缓存就调用 compile 方法拿到 render 函数 的字符串形式,再通过 new Function 的方式生成 render 函数。

 
 
 
 
  1. // 有缓存的话就直接在缓存里面拿
  2. const key = options && options.delimiters
  3.             ? String(options.delimiters) + template
  4.             : template
  5. if (cache[key]) {
  6.     return cache[key]
  7. }
  8. const res = {}
  9. const compiled = compile(template, options) // compile 后面会详细讲
  10. res.render = makeFunction(compiled.render) //通过 new Function 的方式生成 render 函数并缓存
  11. const l = compiled.staticRenderFns.length
  12. res.staticRenderFns = new Array(l)
  13. for (let i = 0; i  l; i++) {
  14.     res.staticRenderFns[i] = makeFunction(compiled.staticRenderFns[i])
  15. }
  16. ......
  17. }
  18. return (cache[key] = res) // 记录至缓存中 

compile 函数就是将 template 编译成 render 函数的字符串形式,后面一小节我们会详细讲到。

完成render方法的生成后,会进入 _mount 中进行DOM更新。该方法的核心逻辑如下:

 
 
 
 
  1. // 触发 beforeMount 生命周期钩子
  2. callHook(vm, 'beforeMount')
  3. // 重点:新建一个 Watcher 并赋值给 vm._watcher
  4. vm._watcher = new Watcher(vm, function updateComponent () {
  5.   vm._update(vm._render(), hydrating)
  6. }, noop)
  7. hydrating = false
  8. // manually mounted instance, call mounted on self
  9. // mounted is called for render-created child components in its inserted hook
  10. if (vm.$vnode == null) {
  11.   vm._isMounted = true
  12.   callHook(vm, 'mounted')
  13. }
  14. return vm 

首先会new一个watcher对象(主要是将模板与数据建立联系),在watcher对象创建后,会运行传入的方法 vm._update(vm._render(), hydrating) 。其中的vm._render()主要作用就是运行前面compiler生成的render方法,并返回一个vNode对象。vm.update() 则会对比新的 vdom 和当前 vdom,并把差异的部分渲染到真正的 DOM 树上。

推荐个图,响应式工程流程

(想深入了解watcher的背后实现原理的,可以观看这篇文章 Vue2.0 源码阅读:响应式原理)

compile

上文中提到 compile 函数就是将 template 编译成 render 函数 的字符串形式。

 
 
 
 
  1. export function compile (
  2.   template: string,
  3.   options: CompilerOptions
  4. ): CompiledResult {
  5.   const AST = parse(template.trim(), options) //1. parse
  6.   optimize(AST, options)  //2.optimize
  7.   const code = generate(AST, options) //3.generate
  8.   return {
  9.     AST,
  10.     render: code.render,
  11.     staticRenderFns: code.staticRenderFns
  12.   }

这个函数主要有三个步骤组成:parse,optimize 和 generate,分别输出一个包含 AST,staticRenderFns 的对象和 render函数 的字符串。

parse 函数,主要功能是将 template字符串解析成 AST。前面定义了ASTElement的数据结构,parse 函数就是将template里的结构(指令,属性,标签等)转换为AST形式存进ASTElement中,最后解析生成AST。

optimize 函数(src/compiler/optimizer.js)主要功能就是标记静态节点,为后面 patch 过程中对比新旧 VNode 树形结构做优化。被标记为 static 的节点在后面的 diff 算法中会被直接忽略,不做详细的比较。

generate 函数(src/compiler/codegen/index.js)主要功能就是根据 AST 结构拼接生成 render 函数的字符串。

 
 
 
 
  1. const code = AST ? genElement(AST) : '_c("div")'
  2. staticRenderFns = prevStaticRenderFns
  3. onceCount = prevOnceCount
  4. return {
  5.     render: `with(this){return ${code}}`, //最外层包一个 with(this) 之后返回
  6.     staticRenderFns: currentStaticRenderFns

其中 genElement 函数(src/compiler/codegen/index.js)是会根据 AST 的属性调用不同的方法生成字符串返回。

 
 
 
 
  1. function genElement (el: ASTElement): string {
  2.   if (el.staticRoot && !el.staticProcessed) {
  3.     return genStatic(el)
  4.   } else if (el.once && !el.onceProcessed) {
  5.     return genOnce(el)
  6.   } else if (el.for && !el.forProcessed) {
  7.     return genFor(el)
  8.   } else if (el.if && !el.ifProcessed) {
  9.     return genIf(el)
  10.   } else if (el.tag === 'template' && !el.slotTarget) {
  11.     return genChildren(el) || 'void 0'
  12.   } else if (el.tag === 'slot') {
  13.   }
  14.     return code
  15.   }

以上就是 compile 函数中三个核心步骤的介绍,compile 之后我们得到了 render 函数 的字符串形式,后面通过 new Function 得到真正的渲染函数。数据发现变化后,会执行 Watcher 中的 _update 函数(src/core/instance/lifecycle.js),_update 函数会执行这个渲染函数,输出一个新的 VNode 树形结构的数据。然后在调用 patch 函数,拿这个新的 VNode 与旧的 VNode 进行对比,只有发生了变化的节点才会被更新到真实 DOM 树上。

patch

patch.js 就是新旧 VNode 对比的 diff 函数,主要是为了优化dom,通过算法使操作dom的行为降到最低,diff 算法来源于 snabbdom,是 VDOM 思想的核心。snabbdom 的算法为了 DOM 操作跨层级增删节点较少的这一目标进行优化,它只会在同层级进行, 不会跨层级比较。

想更加深入VNode diff算法原理的,可以观看(解析vue2.0的diff算法)

总结

  • compile 函数主要是将 template 转换为 AST,优化 AST,再将 AST 转换为 render函数;
  • render函数 与数据通过 Watcher 产生关联;
  • 在数据发生变化时调用 patch 函数,执行此 render 函数,生成新 VNode,与旧 VNode 进行 diff,最终更新 DOM 树。

网站题目:深入Vue2.0底层思想–模板渲染
标题来源:http://www.stwzsj.com/qtweb/news41/10191.html

网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联