1. 程式人生 > 其它 >前端開發系列053-基礎篇之動態響應和繫結

前端開發系列053-基礎篇之動態響應和繫結

title: 前端開發系列053-基礎篇之動態響應和繫結
tags:
categories: []
date: 2017-12-23 00:00:00
本文將繼續討論 前端框架\模板中 文字插值的實現方案,主要關注當資料變化後頁面標籤也實時更新功能,需要指出的是本文面向的是初學者的入門級文章僅僅從[ ( 結果 )...( 實現 ) ]()的角度來討論解決問題的過程,側重點並不在於技術的深度也不探討框架的內部架構和實現原理。

資料和標籤的動態響應

上文中..文字插值 我們已經討論過如何把資料對映到頁面標籤的問題,並且給出了簡陋的Class結構,對於前端框架來說標籤中插值部分能夠跟隨資料實時動態更新是標配的功能,現在我們來考慮下這個功能要如何來實現。

下面是 Vue官網 響應式渲染的說明。

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

簡單點說,我們現在要考慮的是當資料(data的成員)發生變化的時候,頁面掛載標籤中對應的部分也要能夠實時更新。如何實現呢?自然而然的我們能夠想到 —— 監聽資料的變化,當資料變化的時候通知更新UI ( 重新計算和渲染 ) 即可。

監聽物件成員的讀寫操作

如何監聽資料的變化呢?要知道資料都作為例項物件的成員(屬性)而存在的。監聽物件(物件屬性)的讀寫操作以利用Object的靜態方法Object.defineProperty來實現,下面簡單介紹其基本使用。

        var o = {name: "wendingding" };
        Object.defineProperty(o, "name", {
            value: "文頂頂",
            configurable: true,
            enumerable: true,
            writable: true
        })

        console.log(o.name); //"文頂頂"

語法Object.defineProperty(target,key,options)

引數

  * target   目標物件
  * key      物件(成員)屬性名稱
  * options  物件屬性描述物件

用途:更細粒度的定義物件以控制指定屬性的值、是否可配置(刪除)、是否可列舉以及是否可寫。

        Object.defineProperty(o, "name", {
            configurable: true,
            enumerable: true,
            get() {
                console.log("監聽到-執行讀取操作");
            },
            set(newValue) {
                console.log("監聽到-執行寫入操作", newValue);
            }
        })
        console.log(o.name); //監聽到-執行讀取操作
        o.name = "夏"; //監聽到-執行寫入操作 夏

當我們試圖訪問物件屬性的時候可以在get方法中攔截,當嘗試設定物件屬性的時候能夠在set方法中攔截,到這裡我們已經掌握了一種可以監聽物件中屬性讀寫操作的方式。Object.defineProperty方法只能夠對物件中的單個標籤進行監聽,而我們框架的響應模型中需要對物件中的多個數據(建構函式引數物件中data的所有成員)的寫操作進行監聽,怎麼處理呢?

且看下面的實現程式碼。

        
        var o = {name: "xx",age: 18, sex: "女"};

        function definePropertyReact(o, key, val) {
            /* 定義目標物件的單個屬性:(讀 | 寫) 監聽 */
            Object.defineProperty(o, key, {
                configurable: true,
                enumerable: true,
                get() {
                    console.log("監聽到讀取操作", key);
                    return val;
                },
                set(newVal) {
                    console.log("監聽到設定操作", key, newVal);
                    val = newVal;
                }
            })
        }

        /* 迭代物件的成員,為每個資料屬性都監聽監聽 */
        for (const key in o) { definePropertyReact(o, key, o[key]) }

當程式執行後,我們可以在控制檯通過o.name的方式訪問和修改屬性的值,下面是執行的結果。

動態響應的程式碼實現

現在我們已經解決了物件屬性寫操作監聽的問題,剩下的就是當物件中指定屬性的寫操作被觸發時通知讓UI更新即可,下面給出完整的Manager實現和演示程式碼。


    <div id="app">
        <h4>{{title}}</h4>
        <div style="font-size: 12px;color:#195;margin:10px 30px"> {{author}}</div>
        <div> {{textA}}</div>
        <div> {{textB}}</div>
    </div>

    <!-- JS程式碼部分 -->
    <script>
        /* Class的寫法 */
        class Manager {
            constructor(o) {
                /*  根據傳入的el來獲取頁面中掛載的標籤 */
                this.el = document.querySelector(o.el);
                this.rootInnerHTMLCopy = this.el.innerHTML;
                
                /* 存data屬性中所有的屬性名 */
                /* 給data屬性中所有資料新增讀寫操作監聽 */
                this.keys = [];
                for (let key in o.data) {
                    this.keys.push(key);
                    this.definePropertyReact(this, key, o.data[key])
                }
                /* 第一次渲染 */
                this.ObserverPropertyChange();
            }
            definePropertyReact(o, key, val) {
                let self = this;
                
                /* 定義目標物件的單個屬性:(讀 | 寫) 監聽 */
                Object.defineProperty(o, key, {
                    configurable: true,
                    enumerable: true,
                    get() {
                        return val;
                    },
                    set(newVal) {
                        console.log("監聽到設定操作", key, newVal);
                        val = newVal;
                        /* 當修改資料的時候通知UI重新渲染 */
                        self.ObserverPropertyChange();
                    }
                })
            }
            ObserverPropertyChange() {
                this.el.innerHTML = this.rootInnerHTMLCopy;
                this.keys.forEach(ele => {
                    let reg = new RegExp(`{{2}\\s*${ele}\\s*}{2}`, "g");
                    this.el.innerHTML = this.el.innerHTML.replace(reg, this[ele]);
                });
            }
        }

        /* 初始化:傳入配置物件建立例項物件 */
        let app = new Manager({
            el: "#app",
            data: {
                title: "沐春風",
                textA: "白馬秋風塞上",
                textB: "杏花煙雨江南",
                author: "文頂頂"
            }
        })
    </script>