大白話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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...