日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

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

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
聊聊Vue3的模板編譯優(yōu)化

Vue3 正式發(fā)布已經(jīng)有一段時(shí)間了,前段時(shí)間寫了一篇文章(《Vue 模板編譯原理》)分析 Vue 的模板編譯原理。今天的文章打算學(xué)習(xí)下 Vue3 下的模板編譯與 Vue2 下的差異,以及 VDOM 下 Diff 算法的優(yōu)化。

公司主營(yíng)業(yè)務(wù):成都做網(wǎng)站、網(wǎng)站制作、移動(dòng)網(wǎng)站開發(fā)等業(yè)務(wù)。幫助企業(yè)客戶真正實(shí)現(xiàn)互聯(lián)網(wǎng)宣傳,提高企業(yè)的競(jìng)爭(zhēng)能力。成都創(chuàng)新互聯(lián)是一支青春激揚(yáng)、勤奮敬業(yè)、活力青春激揚(yáng)、勤奮敬業(yè)、活力澎湃、和諧高效的團(tuán)隊(duì)。公司秉承以“開放、自由、嚴(yán)謹(jǐn)、自律”為核心的企業(yè)文化,感謝他們對(duì)我們的高要求,感謝他們從不同領(lǐng)域給我們帶來(lái)的挑戰(zhàn),讓我們激情的團(tuán)隊(duì)有機(jī)會(huì)用頭腦與智慧不斷的給客戶帶來(lái)驚喜。成都創(chuàng)新互聯(lián)推出余干免費(fèi)做網(wǎng)站回饋大家。

編譯入口

了解過(guò) Vue3 的同學(xué)肯定知道 Vue3 引入了新的組合 Api,在組件 mount 階段會(huì)調(diào)用 setup 方法,之后會(huì)判斷 render 方法是否存在,如果不存在會(huì)調(diào)用 compile 方法將 template 轉(zhuǎn)化為 render。

 
 
 
 
  1. // packages/runtime-core/src/renderer.ts
  2. const mountComponent = (initialVNode, container) => {
  3.   const instance = (
  4.     initialVNode.component = createComponentInstance(
  5.       // ...params
  6.     )
  7.   )
  8.   // 調(diào)用 setup
  9.   setupComponent(instance)
  10. }
  11. // packages/runtime-core/src/component.ts
  12. let compile
  13. export function registerRuntimeCompiler(_compile) {
  14.   compile = _compile
  15. }
  16. export function setupComponent(instance) {
  17.   const Component = instance.type
  18.   const { setup } = Component
  19.   if (setup) {
  20.     // ...調(diào)用 setup
  21.   }
  22.   if (compile && Component.template && !Component.render) {
  23.    // 如果沒有 render 方法
  24.     // 調(diào)用 compile 將 template 轉(zhuǎn)為 render 方法
  25.     Component.render = compile(Component.template, {...})
  26.   }
  27. }

這部分都是 runtime-core 中的代碼,之前的文章有講過(guò) Vue 分為完整版和 runtime 版本。如果使用 vue-loader 處理 .vue 文件,一般都會(huì)將 .vue 文件中的 template 直接處理成 render 方法。

 
 
 
 
  1. //  需要編譯器
  2. Vue.createApp({
  3.   template: '
    {{ hi }}
    '
  4. })
  5. // 不需要
  6. Vue.createApp({
  7.   render() {
  8.     return Vue.h('div', {}, this.hi)
  9.   }
  10. })

完整版與 runtime 版的差異就是,完整版會(huì)引入 compile 方法,如果是 vue-cli 生成的項(xiàng)目就會(huì)抹去這部分代碼,將 compile 過(guò)程都放到打包的階段,以此優(yōu)化性能。runtime-dom 中提供了 registerRuntimeCompiler 方法用于注入 compile 方法。

主流程

在完整版的 index.js 中,調(diào)用了 registerRuntimeCompiler 將 compile 進(jìn)行注入,接下來(lái)我們看看注入的 compile 方法主要做了什么。

 
 
 
 
  1. // packages/vue/src/index.ts
  2. import { compile } from '@vue/compiler-dom'
  3. // 編譯緩存
  4. const compileCache = Object.create(null)
  5. // 注入 compile 方法
  6. function compileToFunction(
  7.  // 模板
  8.   template: string | HTMLElement,
  9.   // 編譯配置
  10.   options?: CompilerOptions
  11. ): RenderFunction {
  12.   if (!isString(template)) {
  13.     // 如果 template 不是字符串
  14.     // 則認(rèn)為是一個(gè) DOM 節(jié)點(diǎn),獲取 innerHTML
  15.     if (template.nodeType) {
  16.       template = template.innerHTML
  17.     } else {
  18.       return NOOP
  19.     }
  20.   }
  21.   // 如果緩存中存在,直接從緩存中獲取
  22.   const key = template
  23.   const cached = compileCache[key]
  24.   if (cached) {
  25.     return cached
  26.   }
  27.   // 如果是 ID 選擇器,這獲取 DOM 元素后,取 innerHTML
  28.   if (template[0] === '#') {
  29.     const el = document.querySelector(template)
  30.     template = el ? el.innerHTML : ''
  31.   }
  32.   // 調(diào)用 compile 獲取 render code
  33.   const { code } = compile(
  34.     template,
  35.     options
  36.   )
  37.   // 將 render code 轉(zhuǎn)化為 function
  38.   const render = new Function(code)();
  39.  // 返回 render 方法的同時(shí),將其放入緩存
  40.   return (compileCache[key] = render)
  41. }
  42. // 注入 compile
  43. registerRuntimeCompiler(compileToFunction)

在講 Vue2 模板編譯的時(shí)候已經(jīng)講過(guò),compile 方法主要分為三步,Vue3 的邏輯類似:

  1. 模板編譯,將模板代碼轉(zhuǎn)化為 AST;
  2. 優(yōu)化 AST,方便后續(xù)虛擬 DOM 更新;
  3. 生成代碼,將 AST 轉(zhuǎn)化為可執(zhí)行的代碼;
 
 
 
 
  1. // packages/compiler-dom/src/index.ts
  2. import { baseCompile, baseParse } from '@vue/compiler-core'
  3. export function compile(template, options) {
  4.   return baseCompile(template, options)
  5. }
  6. // packages/compiler-core/src/compile.ts
  7. import { baseParse } from './parse'
  8. import { transform } from './transform'
  9. import { transformIf } from './transforms/vIf'
  10. import { transformFor } from './transforms/vFor'
  11. import { transformText } from './transforms/transformText'
  12. import { transformElement } from './transforms/transformElement'
  13. import { transformOn } from './transforms/vOn'
  14. import { transformBind } from './transforms/vBind'
  15. import { transformModel } from './transforms/vModel'
  16. export function baseCompile(template, options) {
  17.   // 解析 html,轉(zhuǎn)化為 ast
  18.   const ast = baseParse(template, options)
  19.   // 優(yōu)化 ast,標(biāo)記靜態(tài)節(jié)點(diǎn)
  20.   transform(ast, {
  21.     ...options,
  22.     nodeTransforms: [
  23.       transformIf,
  24.       transformFor,
  25.       transformText,
  26.       transformElement,
  27.       // ... 省略了部分 transform
  28.     ],
  29.     directiveTransforms: {
  30.       on: transformOn,
  31.       bind: transformBind,
  32.       model: transformModel
  33.     }
  34.   })
  35.   // 將 ast 轉(zhuǎn)化為可執(zhí)行代碼
  36.   return generate(ast, options)
  37. }

計(jì)算 PatchFlag

這里大致的邏輯與之前的并沒有多大的差異,主要是 optimize 方法變成了 transform 方法,而且默認(rèn)會(huì)對(duì)一些模板語(yǔ)法進(jìn)行 transform。這些 transform 就是后續(xù)虛擬 DOM 優(yōu)化的關(guān)鍵,我們先看看 transform 的代碼 。

 
 
 
 
  1. // packages/compiler-core/src/transform.ts
  2. export function transform(root, options) {
  3.   const context = createTransformContext(root, options)
  4.   traverseNode(root, context)
  5. }
  6. export function traverseNode(node, context) {
  7.   context.currentNode = node
  8.   const { nodeTransforms } = context
  9.   const exitFns = []
  10.   for (let i = 0; i < nodeTransforms.length; i++) {
  11.     // Transform 會(huì)返回一個(gè)退出函數(shù),在處理完所有的子節(jié)點(diǎn)后再執(zhí)行
  12.     const onExit = nodeTransforms[i](node, context)
  13.     if (onExit) {
  14.       if (isArray(onExit)) {
  15.         exitFns.push(...onExit)
  16.       } else {
  17.         exitFns.push(onExit)
  18.       }
  19.     }
  20.   }
  21.   traverseChildren(node, context)
  22.   context.currentNode = node
  23.   // 執(zhí)行所以 Transform 的退出函數(shù)
  24.   let i = exitFns.length
  25.   while (i--) {
  26.     exitFns[i]()
  27.   }
  28. }

我們重點(diǎn)看一下 transformElement 的邏輯:

 
 
 
 
  1. // packages/compiler-core/src/transforms/transformElement.ts
  2. export const transformElement: NodeTransform = (node, context) => {
  3.   // transformElement 沒有執(zhí)行任何邏輯,而是直接返回了一個(gè)退出函數(shù)
  4.   // 說(shuō)明 transformElement 需要等所有的子節(jié)點(diǎn)處理完后才執(zhí)行
  5.   return function postTransformElement() {
  6.     const { tag, props } = node
  7.     let vnodeProps
  8.     let vnodePatchFlag
  9.     const vnodeTag = node.tagType === ElementTypes.COMPONENT
  10.       ? resolveComponentType(node, context)
  11.       : `"${tag}"`
  12.     
  13.     let patchFlag = 0
  14.     // 檢測(cè)節(jié)點(diǎn)屬性
  15.     if (props.length > 0) {
  16.       // 檢測(cè)節(jié)點(diǎn)屬性的動(dòng)態(tài)部分
  17.       const propsBuildResult = buildProps(node, context)
  18.       vnodeProps = propsBuildResult.props
  19.       patchFlag = propsBuildResult.patchFlag
  20.     }
  21.     // 檢測(cè)子節(jié)點(diǎn)
  22.     if (node.children.length > 0) {
  23.       if (node.children.length === 1) {
  24.         const child = node.children[0]
  25.         // 檢測(cè)子節(jié)點(diǎn)是否為動(dòng)態(tài)文本
  26.         if (!getStaticType(child)) {
  27.           patchFlag |= PatchFlags.TEXT
  28.         }
  29.       }
  30.     }
  31.     // 格式化 patchFlag
  32.     if (patchFlag !== 0) {
  33.         vnodePatchFlag = String(patchFlag)
  34.     }
  35.     node.codegenNode = createVNodeCall(
  36.       context,
  37.       vnodeTag,
  38.       vnodeProps,
  39.       vnodeChildren,
  40.       vnodePatchFlag
  41.     )
  42.   }
  43. }

buildProps 會(huì)對(duì)節(jié)點(diǎn)的屬性進(jìn)行一次遍歷,由于內(nèi)部源碼涉及很多其他的細(xì)節(jié),這里的代碼是經(jīng)過(guò)簡(jiǎn)化之后的,只保留了 patchFlag 相關(guān)的邏輯。

 
 
 
 
  1. export function buildProps(
  2.   node: ElementNode,
  3.   context: TransformContext,
  4.   props: ElementNode['props'] = node.props
  5. ) {
  6.   let patchFlag = 0
  7.   for (let i = 0; i < props.length; i++) {
  8.     const prop = props[i]
  9.     const [key, name] = prop.name.split(':')
  10.     if (key === 'v-bind' || key === '') {
  11.       if (name === 'class') {
  12.        // 如果包含 :class 屬性,patchFlag | CLASS
  13.         patchFlag |= PatchFlags.CLASS
  14.       } else if (name === 'style') {
  15.        // 如果包含 :style 屬性,patchFlag | STYLE
  16.         patchFlag |= PatchFlags.STYLE
  17.       }
  18.     }
  19.   }
  20.   return {
  21.     patchFlag
  22.   }
  23. }

上面的代碼只展示了三種 patchFlag 的類型:

  • 節(jié)點(diǎn)只有一個(gè)文本子節(jié)點(diǎn),且該文本包含動(dòng)態(tài)的數(shù)據(jù)(TEXT = 1)
 
 
 
 
  1. name: {{name}}

  • 節(jié)點(diǎn)包含可變的 class 屬性(CLASS = 1 << 1)
  •   
      
      
      

節(jié)點(diǎn)包含可變的 style 屬性(STYLE = 1 << 2)

 
 
 
 

可以看到 PatchFlags 都是數(shù)字 1 經(jīng)過(guò) 左移操作符 計(jì)算得到的。

 
 
 
 
  1. export const enum PatchFlags {
  2.   TEXT = 1,             // 1, 二進(jìn)制 0000 0001
  3.   CLASS = 1 << 1,       // 2, 二進(jìn)制 0000 0010
  4.   STYLE = 1 << 2,       // 4, 二進(jìn)制 0000 0100
  5.   PROPS = 1 << 3,       // 8, 二進(jìn)制 0000 1000
  6.   ...
  7. }

從上面的代碼能看出來(lái),patchFlag 的初始值為 0,每次對(duì) patchFlag 都是執(zhí)行 | (或)操作。如果當(dāng)前節(jié)點(diǎn)是一個(gè)只有動(dòng)態(tài)文本子節(jié)點(diǎn)且同時(shí)具有動(dòng)態(tài) style 屬性,最后得到的 patchFlag 為 5(二進(jìn)制:0000 0101)。

 
 
 
 
  1. name: {{name}}

我們將上面的代碼放到 Vue3 中運(yùn)行:

 
 
 
 
  1. const app = Vue.createApp({
  2.   data() {
  3.     return {
  4.       color: 'red',
  5.       name: 'shenfq'
  6.     }
  7.   },
  8.   template: `
  9.    name: {{name}}

  10.   
`
  • })
  • app.mount('#app')
  • 最后生成的 render 方法如下,和我們之前的描述基本一致。

    function render() {}

    render 優(yōu)化

    Vue3 在虛擬 DOM Diff 時(shí),會(huì)取出 patchFlag 和需要進(jìn)行的 diff 類型進(jìn)行 &(與)操作,如果結(jié)果為 true 才進(jìn)入對(duì)應(yīng)的 diff。

    patchFlag 判斷

    還是拿之前的模板舉例:

     
     
     
     
    1. name: {{name}}

    如果此時(shí)的 name 發(fā)生了修改,p 節(jié)點(diǎn)進(jìn)入了 diff 階段,此時(shí)會(huì)將判斷 patchFlag & PatchFlags.TEXT ,這個(gè)時(shí)候結(jié)果為真,表明 p 節(jié)點(diǎn)存在文本修改的情況。

    patchFlag

     
     
     
     
    1. patchFlag = 5
    2. patchFlag & PatchFlags.TEXT
    3. // 或運(yùn)算:只有對(duì)應(yīng)的兩個(gè)二進(jìn)位都為1時(shí),結(jié)果位才為1。
    4. // 0000 0101
    5. // 0000 0001
    6. // ------------
    7. // 0000 0001  =>  十進(jìn)制 1
     
     
     
     
    1. if (patchFlag & PatchFlags.TEXT) {
    2.   if (oldNode.children !== newNode.children) {
    3.     // 修改文本
    4.     hostSetElementText(el, newNode.children)
    5.   }
    6. }

    但是進(jìn)行 patchFlag & PatchFlags.CLASS 判斷時(shí),由于節(jié)點(diǎn)并沒有動(dòng)態(tài) Class,返回值為 0,所以就不會(huì)對(duì)該節(jié)點(diǎn)的 class 屬性進(jìn)行 diff,以此來(lái)優(yōu)化性能。

    patchFlag

     
     
     
     
    1. patchFlag = 5
    2. patchFlag & PatchFlags.CLASS
    3. // 或運(yùn)算:只有對(duì)應(yīng)的兩個(gè)二進(jìn)位都為1時(shí),結(jié)果位才為1。
    4. // 0000 0101
    5. // 0000 0010
    6. // ------------
    7. // 0000 0000  =>  十進(jìn)制 0

    總結(jié)

    其實(shí) Vue3 相關(guān)的性能優(yōu)化有很多,這里只單獨(dú)將 patchFlag 的十分之一的內(nèi)容拿出來(lái)講了,Vue3 還沒正式發(fā)布的時(shí)候就有看到說(shuō) Diff 過(guò)程會(huì)通過(guò) patchFlag 來(lái)進(jìn)行性能優(yōu)化,所以打算看看他的優(yōu)化邏輯,總的來(lái)說(shuō)還是有所收獲。

    本文轉(zhuǎn)載自微信公眾號(hào)「更了不起的前端」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系更了不起的前端公眾號(hào)。


    網(wǎng)頁(yè)名稱:聊聊Vue3的模板編譯優(yōu)化
    文章路徑:http://www.5511xx.com/article/dhocgeo.html