1. 程式人生 > 其它 >前端開發系列054-基礎篇之文字插值

前端開發系列054-基礎篇之文字插值

title: 前端開發系列054-基礎篇之文字插值
tags:
categories: []
date: 2017-12-24 00:00:00
本文討論前端框架\模板中文字插值的實現方案,本文將會主要以[Vue]()框架作為參考討論文字插值語法的具體實現和推導方案,並補充相關的技術細節。

文字插值

在Vue官網文件的第一部分( 宣告式渲染 )我們可以看到下面一段描述。

Vue.js 的核心是一個允許採用簡潔的模板語法來宣告式地將資料渲染進 DOM 的系統:

<div id="app">
  {{ message }}
</div>

var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  }
})
我們已經成功建立了第一個 Vue 應用!
看起來這跟渲染一個字串模板非常類似,但是 Vue 在背後做了大量工作。
現在資料和 DOM 已經被建立了關聯,所有東西都是響應式的。我們要怎麼確認呢?
開啟你的瀏覽器的JavaScript控制檯,並修改 app.message 的值,你將看到上例相應地更新。

在Vue官網的另一部分(模板語法-插值)說明了“ Vue.js 使用了基於 HTML 的模板語法,允許開發者宣告式地將 DOM 繫結至底層 Vue 例項的資料。所有 Vue.js 的模板都是合法的 HTML ,所以能被遵循規範的瀏覽器和 HTML 解析器解析 ”。

我們知道,在Vue框架中資料繫結的插值語法使用的是Mustache語法 (雙大括號) ,而這篇短小的文章將簡單討論其內部的實現機制。

Class-例項的構建初步

# 標籤部分
<div id="app">
  {{ message }}
</div>

# 引入框架檔案
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

# 建立Vue例項
var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  }
})

在Vue框架中我們總是會通過上面的方式來建立並得到一個例項物件,在呼叫的時候我們傳遞了一個物件作為建構函式(class)的引數,在該物件中我們設定了掛載的標籤(el屬性)例項資料(data屬性)等資訊。

這裡,我們先提供一個 建構函式 或者是 Class 來模擬這個整體的結構。

        /* Class的寫法 */
        class Manager {
            constructor(o) {
                /*  根據傳入的el來獲取頁面中掛載的標籤 */
                this.el = document.querySelector(o.el);

                /* 把物件引數中的data成員(資料)新增到例項物件 */
                /* 在訪問的時候可以直接通過(new Manager()).xx訪問 */
                for (let key in o.data) {
                    this[key] = o.data[key];
                }
            }
        }
        /* 初始化:傳入配置物件建立例項物件 */
        let app = new Manager({
            el: "#app",
            data: {
                message: "Hello 文頂頂!"
            }
        })

在開始的時候,[ 建構函式 \ Class ]的樣子可能可能是像上面這樣的,先嚐試獲取引數物件中el的值以獲取例項在頁面中掛載的標籤,然後通過一個迴圈結構來把data中的資料都直接新增到例項物件,這種處理將允許我們直接以app.message的方式來操作資料。

資料和標籤的渲染關係

設計出基本結構後,現在我們可以開始考慮如果需要把data中的資料渲染(繫結)到頁面的標籤,那該如何實現? 簡單思考一秒鐘後,我們似乎可以嘗試以下的實踐策略:

(1) 在初始化的操作中先獲取掛載標籤的屬性節點(這很容易辦到,使用innerHTML就可以)。
(2) 在innerHTML中尋找類類似於{{message}}的結構,如果找到那麼摳出雙括號中的欄位-message
(3) 在例項物件中獲取-message欄位對應的value值,使用該值來替換{{message}}部分。
(!) 因為標籤中可能存在多個插值程式碼,因此可能需要迴圈處理,在尋找插值程式碼的時候使用正則匹配或許會比較合適。

下面試著給出用正則來匹配標籤內容並進行替換的核心程式碼,正則表示式的結果可以參考下面的註釋,用於匹配 {{ xxx }} 的特定結構,\s*表示可以允許存在空格,\\s表示對\進行轉義處理,引數g用以表示應用全域性匹配。

let reg = new RegExp(`{{2}\\s*msg\\s*}{2}`, "g"); // /{{2}\s*msg\s*}{2}/g  
this.el.innerHTML = this.el.innerHTML.replace(reg, "文頂頂");

考慮到在引數物件的data中可能會有多個數據(鍵值對),且執行文字插值的時候某個資料可能會出現在標籤的多個位置,因此需要通過迴圈的方式來檢查 innerTTML 欄位中每個資料的情況。我們可以通過 Object.keys()方法來獲取所有的屬性名(key的集合),然後遍歷該陣列並執行正則替換操作。

<!-- 標籤部分 -->
<div id="app">{{ message }}
        <span>{{message}}</span>
        <span>{{msg}}</span>
</div>
<!-- JS程式碼部分 -->
<script>
        /* Class的寫法 */
        class Manager {
            constructor(o) {
                /*  根據傳入的el來獲取頁面中掛載的標籤 */
                this.el = document.querySelector(o.el);

                /* 把物件引數中的data成員(資料)新增到例項物件 */
                /* 在訪問的時候可以直接通過(new Manager()).xx訪問 */
                for (let key in o.data) {
                    this[key] = o.data[key];
                }
                /* 獲取data資料中所有的key */
                /* 根據data中的屬性集合來遍歷渲染頁面中指定的內容 */
                Object.keys(o.data).forEach(ele => {
                    let reg = new RegExp(`{{2}\\s*${ele}\\s*}{2}`, "g");
                     /*  /{{2}\s*message\s*}{2}/g */
                     /*  /{{2}\s*msg\s*}{2}/g     */
                    console.log(this.el.innerHTML, reg);
                    this.el.innerHTML = this.el.innerHTML.replace(reg, this[ele]);
                })
            }
        }

        /* 初始化:傳入配置物件建立例項物件 */
        let app = new Manager({
            el: "#app",
            data: {
                message: "文頂頂",
                msg: "米桃兒"
            }
        })
</script>

當代碼執行的時候,可以看到下面的效果。

至此,便簡單了實現了資料-標籤渲染的功能。如果資料發生變化後標籤中對應的內容也要隨之變化,這種資料驅動UI的結構最核心之處在於監聽資料的變化並通知給UI檢視,具體實現可以參考下一篇文章。