大白話Vue源碼系列(04):生成render函數

来源:https://www.cnblogs.com/iovec/archive/2017/12/25/vue_04.html
-Advertisement-
Play Games

本來以為 Vue 的編譯器模塊比較好欺負,結果發現並沒有那麼簡單。每一種語法指令都要考慮到,處理起來相當複雜。上篇已經生成了 AST,本篇依然對 Vue 源碼做簡化處理,探究 Vue 是如果根據 AST 生成所需要的 render 函數的。 ...


閱讀目錄

本來以為 Vue 的編譯器模塊比較好欺負,結果發現並沒有那麼簡單。每一種語法指令都要考慮到,處理起來相當複雜。上篇已經生成了 AST,本篇依然對 Vue 源碼做簡化處理,探究 Vue 是如果根據 AST 生成所需要的 render 函數的。

優化 AST

優化 AST 的目的是優化整體性能,避免不必要計算。比如那些不存在數據綁定的節點,即純靜態的(purely static)在更新視圖時根本不需要改變,因此在數據批處理,頁面重渲染時可直接跳過它們。

Vue 通過遍歷 AST 找出內容為純靜態的節點並將其標記為 static:

function optimize (root) {
    //-- 第一步 標記 AST 所有靜態節點 --
    markStatic(root)
    //-- 第二步 標記 AST 所有父節點(即子樹根節點) --
    markStaticRoots(root, false)
}
 

首先標記所有靜態節點:

function markStatic (node) {
    // 標記
    if (node.type === 2) {    // 插值表達式
        node.static = false
    }

    if (node.type === 3) {    // 普通文本
        node.static = true
    }

    if (node.type === 1) {      // 元素
        // 如果所有子節點均是 static,則該節點也是 static
        for (let i = 0; i < node.children.length; i++) {
            const child = node.children[i]
            markStatic(child)
            if (!child.static) {
                node.static = false
            }
        }
    }
}
 

ASTNode 的 type 欄位用於標識節點的類型,可查看上一篇的 AST 節點定義type1 表示元素,type2 表示插值表達式,type3 表示普通文本。可以看到,在標記 ASTElement 時會依次檢查所有子元素節點的靜態標記,從而得出該元素是否為 static。

上面 markStatic 函數使用的是樹形數據結構的深度優先遍歷演算法,使用遞歸實現。

接下來標記出靜態子樹:

function markStaticRoots (node) {
   if (node.type === 1) {
       // For a node to qualify as a static root, it should have children that
       // are not just static text. Otherwise the cost of hoisting out will
       // outweigh the benefits and it's better off to just always render it fresh.
       if (node.static && node.children.length && !(
               node.children.length === 1 &&
               node.children[0].type === 3
           )) {

           node.staticRoot = true
           return
       } else {
           node.staticRoot = false
       }

       for (let i = 0; i < node.children.length; i++) {
           markStaticRoots(node.children[i])
       }
   }
}
 

markStaticRoots 函數里並沒有什麼特別的地方,僅僅是對靜態節點又做了一層篩選。請註意函數中的那幾行註釋:

For a node to qualify as a static root, it should have children that are not just static text. Otherwise the cost of hoisting out will outweigh the benefits and it's better off to just always render it fresh.

翻譯過來大概意思是:一個節點如果想要成為靜態根,它的子節點不能單純只是靜態文本。否則,把它單獨提取出來還不如重渲染時總是更新它性能高。

這也是為什麼要在標記了所有 AST 節點之後又要標記一遍靜態子樹根。

生成 render 函數

不瞭解 render 函數的可以先看一下 Vue 的 render 函數。不瞭解也沒關係,就把它當成 Vue 最終需要的一段特定字元串拼接就行了。

function generate (el) {
    if (el.staticRoot && !el.staticProcessed) {
        return genStatic(el)
    } else if (el.once && !el.onceProcessed) {
        return genOnce(el)
    } else if (el.for && !el.forProcessed) {
        return genFor(el)
    } else if (el.if && !el.ifProcessed) {
        return genIf(el)
    } else if (el.tag === 'template' && !el.slotTarget) {
        return genChildren(el) || 'void 0'
    } else if (el.tag === 'slot') {
        return genSlot(el)
    } else {
        // component or element
        let code
        if (el.component) {
            code = genComponent(el.component, el)
        } else {
            const data = el.plain ? undefined : genData(el)

            const children = el.inlineTemplate ? null : genChildren(el)
            code = `_c('${el.tag}'${
                    data ? `,${data}` : '' // data
                }${
                    children ? `,${children}` : '' // children
                })`
        }
        return code
    }
}
 

上面生成 render 函數的過程比較繁瑣,需要對不同情況作單獨處理,這裡不再一一展開。感興趣的可在 Vue 項目的 src/compiler/codegen/index.js 文件里仔細研究一下。

現在結合生成 AST 的方法就可以將編譯器初探中遺留的 compileToFunctions 方法定義出來了:

function compileToFunctions(el){
    let ast = parseHTML(el)
    optimize(ast)
    return generate(ast)
}
 

也就是之前說的三步走:

  1. 將 html 模板解析成抽象語法樹(AST)。
  2. 對 AST 做優化處理。
  3. 根據 AST 生成 render 函數。

小結

Vue 首先根據使用者的傳參獲得待編譯的模板片段,然後使用正則匹配對片段里的標簽各個擊破,用步步蠶食的方法將整塊模板片段最終解析成一棵 AST。獲得 AST 後,後續的處理就非常方便了。Vue 會首先優化這棵 AST,將其中的靜態子樹找出來,這些靜態節點在之後的視圖更新和數據計算中是可以忽略掉的,從而提高性能。最後 Vue 遍歷這棵 AST 的每個節點,根據節點的類型生成不同的函數碎片,最後拼接成整個 render 函數。

值得一提的是,將 AST 作為編譯的中間形式是非常方便的,當 AST 構建出來之後,使用樹形結構的深度優先遍歷演算法就可以方便地對樹的每一個節點做處理。Vue 最後生成 render 函數也是通過遍歷 AST ,根據每個節點生成函數的一小部分,最後拼接成整個函數。

本篇完,Vue 編譯器部分到此完結。將在下篇開始進行 Vue 運行時相關的代碼剖析,運行時這部分代碼應該會是非常有意思的,包括 Vue 對象實例化,雙向綁定,虛擬 DOM 等內容。

大白話 Vue 源碼系列目錄

本系列會以每周一篇的速度持續更新,喜歡的小伙伴記得點關註哦。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 第一步處理rule為字元串,直接返回一個包裝類,很簡單看註釋就好了。 test 然後處理test、include、exclude,如下: checkResourceSource直接看源碼: 這個用於檢測配置來源的唯一性,後面會能看到作用,同樣作用的還有checkUseSource方法。 隨後將三個參 ...
  • Tips:寫到這裡,需要對當初的規則進行修改。在必要的地方,會在webpack.config.js中設置特殊的參數來跑源碼,例如本例會使用module:{rules:[...]}來測試,基本上測試參數均取自於vue腳手架(太複雜的刪掉)。 下麵兩節的主要流程圖如下: 在進入compile方法後,迎面 ...
  • 解釋說明: 先將字元串轉化為數組,然後將其轉化為小寫,這裡用到str.toLowerCase()和str.split(" "),split分割一定要用空格隔開,然後用for迴圈遍曆數組中每個元素,將每個元素的首字母賦值給變數char,再用toUpperCase()函數將首字母大寫,再將每個元素重新... ...
  • JavaScript本身可通過charCodeAt方法得到一個字元的Unicode編碼,並通過fromCharCode方法將Unicode編碼轉換成對應字元。 但charCodeAt方法得到的應該是一個16位的整數,每個字元占用兩位元組。在網路上傳輸一般採用UTF 8編碼,JavaScript本身沒有 ...
  • 前言 BFC 的概念始於 CSS2,是個蠻古老的 CSS 話題了,網上也到處能搜到 BFC 的介紹,但是都不夠簡潔。本文系翻譯自 Rachel Andrew 女士的博文 Understanding CSS Layout And The Block Formatting Context,內容足夠簡潔明 ...
  • 首先我們先要導入幾張圖片(我已導入完畢): ; 好,我們先寫一個 定義一個 在這<div>中再定義一個<div></div>,定義一個id="hhs"(隨便定義的),然後在下麵定義五個圖片 我們再在樣式表中寫上一些需要用的,如下代碼: 我們寫一個 1 function $(oId){ 2 retur ...
  • //1var name='world';(function(){ if(typeof name 'undefined'){ var name='jack'; console.log('Goodbye'+name); }else{ console.log('Hello'+name); }})();// ...
  • 轉自腳本之家: 本篇文章主要介紹了angular中實現li或者某個元素點擊變色的兩種方法,非常具有實用價值,需要的朋友可以參考下 本文介紹了angular中實現li或者某個元素點擊變色的兩種方法,分享給大家,希望對大家有幫助 先說一種最直接了當的不需要js控制。 方法一:直接在用ng-class就可 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...