日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
深入Vue2.0底層思想–模板渲染

初衷

原州ssl適用于網(wǎng)站、小程序/APP、API接口等需要進(jìn)行數(shù)據(jù)傳輸應(yīng)用場景,ssl證書未來市場廣闊!成為創(chuàng)新互聯(lián)公司的ssl證書銷售渠道,可以享受市場價(jià)格4-6折優(yōu)惠!如果有意向歡迎電話聯(lián)系或者加微信:13518219792(備注:SSL證書合作)期待與您的合作!

在使用vue2.0的過程,有時看API很難理解vue作者的思想,這促使我想要去深入了解vue底層的思想,了解完底層的一些思想,才能更好的用活框架,雖然網(wǎng)上已經(jīng)有很多源碼解析的文檔,但我覺得只有自己動手了,才能更加深印象。

vue2.0和1.0模板渲染的區(qū)別

Vue 2.0 中模板渲染與 Vue 1.0 完全不同,1.0 中采用的 DocumentFragment (想了解可以觀看這篇文章),而 2.0 中借鑒 React 的 Virtual DOM?;?Virtual DOM,2.0 還可以支持服務(wù)端渲染(SSR),也支持 JSX 語法。

知識普及

在開始閱讀源碼之前,先了解一些相關(guān)的知識:AST 數(shù)據(jù)結(jié)構(gòu),VNode 數(shù)據(jù)結(jié)構(gòu),createElement 的問題,render函數(shù)。

AST 數(shù)據(jù)結(jié)構(gòu)

AST 的全稱是 Abstract Syntax Tree(抽象語法樹),是源代碼的抽象語法結(jié)構(gòu)的樹狀表現(xiàn)形式,計(jì)算機(jī)學(xué)科中編譯原理的概念。而vue就是將模板代碼映射為AST數(shù)據(jù)結(jié)構(gòu),進(jìn)行語法解析。

我們看一下 Vue 2.0 源碼中 AST 數(shù)據(jù)結(jié)構(gòu) 的定義:

 
 
 
 
  1. declare type ASTNode = ASTElement | ASTText | ASTExpression
  2. declare type ASTElement = { // 有關(guān)元素的一些定義
  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 區(qū)分。

VNode數(shù)據(jù)結(jié)構(gòu)

下面是 Vue 2.0 源碼中 VNode 數(shù)據(jù)結(jié)構(gòu) 的定義 (帶注釋的跟下面介紹的內(nèi)容有關(guān)):

 
 
 
 
  1. constructor {
  2.   this.tag = tag   //元素標(biāo)簽
  3.   this.data = data  //屬性
  4.   this.children = children  //子元素列表
  5.   this.text = text
  6.   this.elm = elm  //對應(yīng)的真實(shí) 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 //是否被標(biāo)記為靜態(tài)節(jié)點(diǎn)
  16.   this.isRootInsert = true
  17.   this.isComment = false
  18.   this.isCloned = false
  19.   this.isOnce = false

真實(shí)DOM存在什么問題,為什么要用虛擬DOM

我們?yōu)槭裁床恢苯邮褂迷?DOM 元素,而是使用真實(shí) DOM 元素的簡化版 VNode,最大的原因就是 document.createElement 這個方法創(chuàng)建的真實(shí) DOM 元素會帶來性能上的損失。我們來看一個 document.createElement 方法的例子

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

打開 console 運(yùn)行一下上面的代碼,會發(fā)現(xiàn)打印出來的屬性多達(dá) 228 個,而這些屬性有 90% 多對我們來說都是無用的。VNode 就是簡化版的真實(shí) DOM 元素,關(guān)聯(lián)著真實(shí)的dom,比如屬性elm,只包括我們需要的屬性,并新增了一些在 diff 過程中需要使用的屬性,例如 isStatic。

render函數(shù)

這個函數(shù)是通過編譯模板文件得到的,其運(yùn)行結(jié)果是 VNode。render 函數(shù) 與 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函數(shù)

 
 
 
 
  1. (function() {
  2.   with(this){
  3.     return _c('div',{   //創(chuàng)建一個 div 元素
  4.       attrs:{"id":"app"}  //div 添加屬性 id
  5.       },[
  6.         _m(0),  //靜態(tài)節(jié)點(diǎn) header,此處對應(yīng) staticRenderFns 數(shù)組索引為 0 的 render 函數(shù)
  7.         _v(" "), //空的文本節(jié)點(diǎn)
  8.         (message) //三元表達(dá)式,判斷 message 是否存在
  9.          //如果存在,創(chuàng)建 p 元素,元素里面有文本,值為 toString(message)
  10.         ?_c('p',[_v("\n    "+_s(message)+"\n  ")])
  11.         //如果不存在,創(chuàng)建 p 元素,元素里面有文本,值為 No message.
  12.         :_c('p',[_v("\n    No message.\n  ")])
  13.       ]
  14.     )
  15.   }
  16. }) 

要看懂上面的 render函數(shù),只需要了解 _c,_m,_v,_s 這幾個函數(shù)的定義,其中 _c 是 createElement(創(chuàng)建元素),_m 是 renderStatic(渲染靜態(tài)節(jié)點(diǎn)),_v 是 createTextVNode(創(chuàng)建文本dom),_s 是 toString (轉(zhuǎn)換為字符串)

除了 render 函數(shù),還有一個 staticRenderFns 數(shù)組,這個數(shù)組中的函數(shù)與 VDOM 中的 diff 算法優(yōu)化相關(guān),我們會在編譯階段給后面不會發(fā)生變化的 VNode 節(jié)點(diǎn)打上 static 為 true 的標(biāo)簽,那些被標(biāo)記為靜態(tài)節(jié)點(diǎn)的 VNode 就會單獨(dú)生成 staticRenderFns 函數(shù)

 
 
 
 
  1. (function() { //上面 render 函數(shù) 中的 _m(0) 會調(diào)用這個方法
  2.   with(this){
  3.     return _c('header',[_c('h1',[_v("I'm a template!")])])
  4.   }
  5. }) 

模板渲染過程(重要的函數(shù)介紹)

了解完一些基礎(chǔ)知識后,接下來我們講解下模板的渲染過程

$mount 函數(shù),主要是獲取 template,然后進(jìn)入 compileToFunctions 函數(shù)。

compileToFunctions 函數(shù),主要將 template 編譯成 render 函數(shù)。首先讀緩存,沒有緩存就調(diào)用 compile 方法拿到 render 函數(shù) 的字符串形式,再通過 new Function 的方式生成 render 函數(shù)。

 
 
 
 
  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 后面會詳細(xì)講
  10. res.render = makeFunction(compiled.render) //通過 new Function 的方式生成 render 函數(shù)并緩存
  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 函數(shù)就是將 template 編譯成 render 函數(shù)的字符串形式,后面一小節(jié)我們會詳細(xì)講到。

完成render方法的生成后,會進(jìn)入 _mount 中進(jìn)行DOM更新。該方法的核心邏輯如下:

 
 
 
 
  1. // 觸發(fā) beforeMount 生命周期鉤子
  2. callHook(vm, 'beforeMount')
  3. // 重點(diǎn):新建一個 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對象(主要是將模板與數(shù)據(jù)建立聯(lián)系),在watcher對象創(chuàng)建后,會運(yùn)行傳入的方法 vm._update(vm._render(), hydrating) 。其中的vm._render()主要作用就是運(yùn)行前面compiler生成的render方法,并返回一個vNode對象。vm.update() 則會對比新的 vdom 和當(dāng)前 vdom,并把差異的部分渲染到真正的 DOM 樹上。

推薦個圖,響應(yīng)式工程流程

(想深入了解watcher的背后實(shí)現(xiàn)原理的,可以觀看這篇文章 Vue2.0 源碼閱讀:響應(yīng)式原理)

compile

上文中提到 compile 函數(shù)就是將 template 編譯成 render 函數(shù) 的字符串形式。

 
 
 
 
  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.   }

這個函數(shù)主要有三個步驟組成:parse,optimize 和 generate,分別輸出一個包含 AST,staticRenderFns 的對象和 render函數(shù) 的字符串。

parse 函數(shù),主要功能是將 template字符串解析成 AST。前面定義了ASTElement的數(shù)據(jù)結(jié)構(gòu),parse 函數(shù)就是將template里的結(jié)構(gòu)(指令,屬性,標(biāo)簽等)轉(zhuǎn)換為AST形式存進(jìn)ASTElement中,最后解析生成AST。

optimize 函數(shù)(src/compiler/optimizer.js)主要功能就是標(biāo)記靜態(tài)節(jié)點(diǎn),為后面 patch 過程中對比新舊 VNode 樹形結(jié)構(gòu)做優(yōu)化。被標(biāo)記為 static 的節(jié)點(diǎn)在后面的 diff 算法中會被直接忽略,不做詳細(xì)的比較。

generate 函數(shù)(src/compiler/codegen/index.js)主要功能就是根據(jù) AST 結(jié)構(gòu)拼接生成 render 函數(shù)的字符串。

 
 
 
 
  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 函數(shù)(src/compiler/codegen/index.js)是會根據(jù) AST 的屬性調(diào)用不同的方法生成字符串返回。

 
 
 
 
  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 函數(shù)中三個核心步驟的介紹,compile 之后我們得到了 render 函數(shù) 的字符串形式,后面通過 new Function 得到真正的渲染函數(shù)。數(shù)據(jù)發(fā)現(xiàn)變化后,會執(zhí)行 Watcher 中的 _update 函數(shù)(src/core/instance/lifecycle.js),_update 函數(shù)會執(zhí)行這個渲染函數(shù),輸出一個新的 VNode 樹形結(jié)構(gòu)的數(shù)據(jù)。然后在調(diào)用 patch 函數(shù),拿這個新的 VNode 與舊的 VNode 進(jìn)行對比,只有發(fā)生了變化的節(jié)點(diǎn)才會被更新到真實(shí) DOM 樹上。

patch

patch.js 就是新舊 VNode 對比的 diff 函數(shù),主要是為了優(yōu)化dom,通過算法使操作dom的行為降到最低,diff 算法來源于 snabbdom,是 VDOM 思想的核心。snabbdom 的算法為了 DOM 操作跨層級增刪節(jié)點(diǎn)較少的這一目標(biāo)進(jìn)行優(yōu)化,它只會在同層級進(jìn)行, 不會跨層級比較。

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

總結(jié)

  • compile 函數(shù)主要是將 template 轉(zhuǎn)換為 AST,優(yōu)化 AST,再將 AST 轉(zhuǎn)換為 render函數(shù);
  • render函數(shù) 與數(shù)據(jù)通過 Watcher 產(chǎn)生關(guān)聯(lián);
  • 在數(shù)據(jù)發(fā)生變化時調(diào)用 patch 函數(shù),執(zhí)行此 render 函數(shù),生成新 VNode,與舊 VNode 進(jìn)行 diff,最終更新 DOM 樹。

新聞名稱:深入Vue2.0底層思想–模板渲染
本文鏈接:http://www.dlmjj.cn/article/djhcoej.html