Vue源碼學習(二):<templete>渲染第一步,模板解析

来源:https://www.cnblogs.com/FatTiger4399/archive/2023/09/06/17681865.html
-Advertisement-
Play Games

好家伙, 1.<template>去哪了 在正式內容之前,我們來思考一個問題, 當我們使用vue開發頁面時,<tamplete>中的內容是如何變成我們網頁中的內容的? 它會經歷四步: 解析模板:Vue會解析<template>中的內容,識別出其中的指令、插值表達式({{}}),以及其他元素和屬性。 ...


好家伙,

 

1.<template>去哪了

在正式內容之前,我們來思考一個問題,

當我們使用vue開發頁面時,<tamplete>中的內容是如何變成我們網頁中的內容的?

 

它會經歷四步:

  1. 解析模板:Vue會解析<template>中的內容,識別出其中的指令、插值表達式({{}}),以及其他元素和屬性。

  2. 生成AST:解析模板後,Vue會生成一個對應的AST(Abstract Syntax Tree,抽象語法樹),用於表示模板的結構、指令、屬性等信息。

  3. 生成渲染函數:根據生成的AST,Vue會生成渲染函數。渲染函數是一個函數,接收一些數據作為參數,並返回一個虛擬DOM(Virtual DOM)。

  4. 渲染到真實DOM:Vue執行渲染函數,將虛擬DOM轉換為真實的DOM,並將其插入到頁面中的指定位置。在這個過程中,Vue會根據數據的變化重新執行渲染函數,更新頁面上的內容。

所以,步驟如下:模板解析 =》AST =》生成渲染函數 =》渲染到真實DOM

 

 

2.ast語法樹是什麼?

抽象語法樹(abstract syntax code,AST)是源代碼的抽象語法結構的樹狀表示,樹上的每個節點都表示源代碼中的一種結構,之所以說是抽象的,抽象表示把js代碼進行了結構化的轉化,轉化為一種數據結構

這種數據結構其實就是一個大的json對象,json我們都熟悉,他就像一顆枝繁葉茂的樹。有樹根,有樹幹,有樹枝,有樹葉,無論多小多大,都是一棵完整的樹。

簡單理解,就是把我們寫的代碼按照一定的規則轉換成一種樹形結構。

舉個簡單的例子:

假設代碼如下:

<div id="app">Hello</div>

 

隨後我們將其轉換為ast語法樹(簡單版本):

 {
     tag:'div'    //節點類型
     attrs:[{id:"app"}]    //屬性
     children:[{tag:null,text:Hello},{xxx}]   //子節點
 }

 

當然,實際情況複雜得多,但總體結構不變

{
  "type": "Program",
  "start": 0,
  "end": 32,
  "body": [
    {
      "type": "ExpressionStatement",
      "expression": {
        "type": "JSXElement",
        "openingElement": {
          "type": "JSXOpeningElement",
          "name": {
            "type": "JSXIdentifier",
            "name": "div"
          },
          "attributes": [
            {
              "type": "JSXAttribute",
              "name": {
                "type": "JSXIdentifier",
                "name": "id"
              },
              "value": {
                "type": "Literal",
                "value": "app"
              }
            }
          ],
          "selfClosing": false
        },
        "closingElement": {
          "type": "JSXClosingElement",
          "name": {
            "type": "JSXIdentifier",
            "name": "div"
          }
        },
        "children": [
          {
            "type": "JSXText",
            "value": "Hello"
          },
          {
            "type": "JSXExpressionContainer",
            "expression": {
              "type": "Identifier",
              "name": "msg"
            }
          }
        ],
        "selfClosing": false
      }
    }
  ],
  "sourceType": "module"
}

 

2.模板解析

來看這個例子

<div id="app">Hello{{msg}}</div>

這無非就是一個簡單的<div>標簽,它由三個部分組成

開始標簽:

<div id="app">

文本:

Hello{{msg}}

結束標簽:

</div>

 

似乎只要用正則表達式來匹配就可以了,(事實上也確實是這麼實現的)

//從源碼處偷過來的正則表達式
const attribute =
    /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/
//屬性 例如:  {id=app}
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`; //標簽名稱
const qnameCapture = `((?:${ncname}\\:)?${ncname})` //<span:xx>
const startTagOpen = new RegExp(`^<${qnameCapture}`) //標簽開頭
const startTagClose = /^\s*(\/?)>/ //匹配結束標簽 的 >
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`) //結束標簽 例如</div>
const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g

 

2.1.試驗實例

我們來舉一個實例看看:

代碼已開源https://github.com/Fattiger4399/analytic-vue.git

(關鍵的部分已使用綠色熒光標出,沒有耐心看完整代碼的話,只看有綠色熒游標記的部分就好)

項目目錄如下:

首先來到index.html我們人為的製造一些假數據

註意:此處的vue是我們自己寫的實驗品,並非大尤的Vue

index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="app">Hello{{msg}}</div>
    <script src="dist/vue.js"></script>
    <script>
        //umd Vue
        // console.log(Vue)
        //響應式 Vue
        let vm = new Vue({
            el: '#app', //編譯模板
        })

    </script>
</body>

</html>

 

入口文件index.js

import {initMixin} from "./init"

function Vue(options) {
    // console.log(options)
    //初始化
    this._init(options)
}
initMixin(Vue)
export default Vue

 

初始化腳本init.js

import { compileToFunction } from "./compile/index.js";

export function initMixin(Vue) {
    Vue.prototype._init = function (options) {
        // console.log(options)
        let vm = this
        //options為
        vm.$options = options
        //初始化狀態
        initState(vm)

        // 渲染模板 el
        if (vm.$options.el) {
            vm.$mount(vm.$options.el)
        }
    }
    //創建 $mount

    Vue.prototype.$mount = function (el) {
        // console.log(el)
        //el template render
        let vm = this
        el = document.querySelector(el) //獲取元素
        let options = vm.$options
        if (!options.render) { //沒有
            let template = options.template
            if (!template && el) {
                //獲取html
                el = el.outerHTML
                console.log(el,'this is init.js attrs:el')
                //<div id="app">Hello</div>
                //變成ast語法樹
                let ast = compileToFunction(el)
                console.log(ast,'this is ast')
                //render()
            }
        }
    }
    
}

 

來到我們的核心部分/compile/index.js中的parseHTML()方法和parseStartTag()方法

function start(tag, attrs) { //開始標簽
    console.log(tag, attrs, '開始的標簽')
}

function charts(text) { //獲取文本
    console.log(text, '文本')
}

function end(tag) { //結束的標簽
    console.log(tag, '結束標簽')
}
function parseHTML(html) {
    while (html) { //html 為空時,結束
        //判斷標簽 <>
        let textEnd = html.indexOf('<') //0
        console.log(html,textEnd,'this is textEnd')
        if (textEnd === 0) { //標簽
            // (1) 開始標簽
            const startTagMatch = parseStartTag() //開始標簽的內容{}
            if (startTagMatch) {
                start(startTagMatch.tagName, startTagMatch.attrs);
                continue;
            }
            // console.log(endTagMatch, '結束標簽')
            //結束標簽
            let endTagMatch = html.match(endTag)
            if (endTagMatch) {
                advance(endTagMatch[0].length)
                end(endTagMatch[1])
                continue;
            }
        }
        let text
        //文本
        if (textEnd > 0) {
            // console.log(textEnd)
            //獲取文本內容
            text = html.substring(0, textEnd)
            // console.log(text)
        }
        if (text) {
            advance(text.length)
            charts(text)
            // console.log(html)
        }
    }
    function parseStartTag() {
        //
        const start = html.match(startTagOpen) // 1結果 2false
        console.log(start,'this is start')
        // match() 方法檢索字元串與正則表達式進行匹配的結果
        // console.log(start)
        //創建ast 語法樹
        if (start) {
            let match = {
                tagName: start[1],
                attrs: []
            }
            console.log(match,'match match')
            //刪除 開始標簽
            advance(start[0].length)
            //屬性
            //註意 多個 遍歷
            //註意>
            let attr //屬性 
            let end //結束標簽
            //attr=html.match(attribute)用於匹配
            //非結束位'>',且有屬性存在
            while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {
                // console.log(attr,'attr attr'); //{}
                // console.log(end,'end end')
                match.attrs.push({
                    name: attr[1],
                    value: attr[3] || attr[4] || attr[5]
                })
                advance(attr[0].length)
                //匹配完後,就進行刪除操作
            }
            //end裡面有東西了(只能是有">"),那麼將其刪除
            if (end) {
                // console.log(end)
                advance(end[0].length)
                return match
            }
        }
    }
    function advance(n) {
        // console.log(html)
        // console.log(n)
        html = html.substring(n)
        // substring() 方法返回一個字元串在開始索引到結束索引之間的一個子集,
        // 或從開始索引直到字元串的末尾的一個子集。
        // console.log(html)
    }
    console.log(root)
    return root 
}
export function compileToFunction(el) {
    // console.log(el)
    let ast = parseHTML(el)
    console.log(ast,'ast ast')
}

 

註釋已經非常詳細了,實在看不懂的話,上機調試一遍吧

代碼已開源https://github.com/Fattiger4399/analytic-vue.git

tips:

(1)parseHTML中拿到的參數html為 "  el = el.outerHTML  " 獲取的元素

即' <div id="app">Hello{{msg}}</div> '

 

(2)attr = html.match(attribute)匹配後得到的數據長這樣:

 

 

來看看看輸出結果

成功地將我們需要的三樣東西分出來了 

 


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

-Advertisement-
Play Games
更多相關文章
  • 本文介紹了非連續空間存放方式中的兩種常見形式:鏈式分配和索引分配。鏈式分配通過鏈表的方式實現了文件的非連續分配,其中包括了隱式鏈接和顯式鏈接兩種方式。隱式鏈接通過遍歷鏈表來獲取下一個節點的指針,適合於文件的擴展,但查找效率較低。顯式鏈接則將指針存儲在文件分配表中,提高了檢索速度,但不適用於大磁碟空間... ...
  • ![](https://img2023.cnblogs.com/blog/3076680/202309/3076680-20230905123241037-972099517.png) # 1. 基本信息 SQL學習指南(第3版) Learning SQL, Third Edition [美] 艾倫 ...
  • # 調研背景: 資料庫連接建立是比較昂貴的操作(至少對於 OLTP),不僅要建立 TCP 連接外還需要進行連接鑒權操作,所以客戶端通常會把資料庫連接保存到連接池中進行復用。連接池維護到彈性資料庫(JED)的長連接,彈性資料庫預設不會主動關閉客戶端連接(除非報錯),但一般客戶端到彈性資料庫之間還會有負 ...
  • ![file](https://img2023.cnblogs.com/other/2685289/202309/2685289-20230906144112614-1233246750.png) ## 導讀 蜀海供應鏈是集銷售、研發、採購、生產、品保、倉儲、運輸、信息、金融為一體的餐飲供應鏈服務企 ...
  • ![file](https://img2023.cnblogs.com/other/2685289/202309/2685289-20230906105454530-376816477.jpg) > 導讀:國內某頭部理財服務提供商成立於 2019 年,是股份制銀行中首批獲准籌建、首家獲准開業、首家成 ...
  • 當談到[數據湖](https://www.dtstack.com/dtengine/easylake?src=szsm)的時候,大家都在說,可以把所有數據(結構化/半結構化/非結構化)一股腦都丟進去,進行統一的元數據管理。然後上層計算對接,進行[流批計算](https://www.dtstack.c ...
  • 浙江省工業和信息化廳開展了2023第二季度創新型中小企業評價工作,玖章算術以優秀的自主創新能力通過認定,成為浙江省2023年度創新型中小企業。玖章算術聚焦於雲計算與數據管理基礎技術領域,擁有豐富的研發經驗和專業技術團隊。NineData是新一代的雲原生智能數據管理平臺,包含了數據複製、SQL開發、數... ...
  • `` 數組的includes方法在日常的編程中比較常用到,其作用就是判斷某一數據是否在數組中,通常來說,數組中的數據如果是數字,布爾值,或者字元串的話,都是能夠進行判斷的 例如: ``` [1,2,3,4].includes(3) // true [1,2,3,4].includes(5) // f ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...