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 微服務框架,幫助我們輕鬆構建和管理微服務應用。 本框架不僅支持 Consul 服務註 ...
  • 先看一下效果吧: 如果不會寫動畫或者懶得寫動畫,就直接交給Blend來做吧; 其實Blend操作起來很簡單,有點類似於在操作PS,我們只需要設置關鍵幀,滑鼠點來點去就可以了,Blend會自動幫我們生成我們想要的動畫效果. 第一步:要創建一個空的WPF項目 第二步:右鍵我們的項目,在最下方有一個,在B ...
  • Prism:框架介紹與安裝 什麼是Prism? Prism是一個用於在 WPF、Xamarin Form、Uno 平臺和 WinUI 中構建鬆散耦合、可維護和可測試的 XAML 應用程式框架 Github https://github.com/PrismLibrary/Prism NuGet htt ...
  • 在WPF中,屏幕上的所有內容,都是通過畫筆(Brush)畫上去的。如按鈕的背景色,邊框,文本框的前景和形狀填充。藉助畫筆,可以繪製頁面上的所有UI對象。不同畫筆具有不同類型的輸出( 如:某些畫筆使用純色繪製區域,其他畫筆使用漸變、圖案、圖像或繪圖)。 ...
  • 前言 嗨,大家好!推薦一個基於 .NET 8 的高併發微服務電商系統,涵蓋了商品、訂單、會員、服務、財務等50多種實用功能。 項目不僅使用了 .NET 8 的最新特性,還集成了AutoFac、DotLiquid、HangFire、Nlog、Jwt、LayUIAdmin、SqlSugar、MySQL、 ...
  • 本文主要介紹攝像頭(相機)如何採集數據,用於類似攝像頭本地顯示軟體,以及流媒體數據傳輸場景如傳屏、視訊會議等。 攝像頭採集有多種方案,如AForge.NET、WPFMediaKit、OpenCvSharp、EmguCv、DirectShow.NET、MediaCaptre(UWP),網上一些文章以及 ...
  • 前言 Seal-Report 是一款.NET 開源報表工具,擁有 1.4K Star。它提供了一個完整的框架,使用 C# 編寫,最新的版本採用的是 .NET 8.0 。 它能夠高效地從各種資料庫或 NoSQL 數據源生成日常報表,並支持執行複雜的報表任務。 其簡單易用的安裝過程和直觀的設計界面,我們 ...
  • 背景需求: 系統需要對接到XXX官方的API,但因此官方對接以及管理都十分嚴格。而本人部門的系統中包含諸多子系統,系統間為了穩定,程式間多數固定Token+特殊驗證進行調用,且後期還要提供給其他兄弟部門系統共同調用。 原則上:每套系統都必須單獨接入到官方,但官方的接入複雜,還要官方指定機構認證的證書 ...
  • 本文介紹下電腦設備關機的情況下如何通過網路喚醒設備,之前電源S狀態 電腦Power電源狀態- 唐宋元明清2188 - 博客園 (cnblogs.com) 有介紹過遠程喚醒設備,後面這倆天瞭解多了點所以單獨加個隨筆 設備關機的情況下,使用網路喚醒的前提條件: 1. 被喚醒設備需要支持這WakeOnL ...
  • 前言 大家好,推薦一個.NET 8.0 為核心,結合前端 Vue 框架,實現了前後端完全分離的設計理念。它不僅提供了強大的基礎功能支持,如許可權管理、代碼生成器等,還通過採用主流技術和最佳實踐,顯著降低了開發難度,加快了項目交付速度。 如果你需要一個高效的開發解決方案,本框架能幫助大家輕鬆應對挑戰,實 ...