百度前端學院任務動態資料繫結(五)

任乃千發表於2019-02-27

覺得這個系列任務還是很有趣的,這是一種效能很差的實現,也許之後會嘗試使用虛擬dom、改善其中的遍歷。

百度前端學院任務動態資料繫結(五)
任務資訊


首先分析一下要幹嘛:可以看出Vue是個建構函式;因為傳入的物件可能有很多層物件,所以需要一個遍歷傳入物件的方法;雙向繫結打算通過訪問器屬性實現、需要重新整理dom,所以要有能重新整理dom的方法、以及把傳入Vue建構函式的物件中的值轉換成Vue例項中的帶有重新整理dom回撥的訪問器的方法;最後還要一個初始化的方法,來保證沒觸發set時的初次訪問的渲染;

//    Vue建構函式將會把入口(entry)和data例項化
    let Vue = function (obj) {
        this.entry = document.querySelector(obj.el);
        this.data = {};
        this.init(obj);
    };

//    初始化,遍歷傳入的物件轉換成例項上的訪問器,渲染一下頁面
    Vue.prototype.init=function (obj) {
        this.walk(this.data, obj.data);
        this.render(this.data, this.entry);
    };複製程式碼

因為渲染時需要入口,所以把相應的入口例項化;把用來雙向繫結的資料放到data裡,這樣語義化一點(之前任務要求);呼叫一下Vue原型物件上的init方法。通用的方法沒必要例項化,放在原型物件上就好。

//    用來遍歷輸入的物件
    Vue.prototype.walk = function (output, input) {
        for (let key in input) {
            if (input.hasOwnProperty(key)) {
                if (typeof input[key] !== `object` || input[key] === null) {
                    this.convert(output, key, input[key]);
                } else {
                    this.walk(output[key] = {}, input[key]);
                }
            }
        }
    };複製程式碼

遍歷輸入上的每一個屬性,使用hasOwnProperty判斷如果是它自己的屬性進入下一步判斷,用來過濾掉繼承的屬性。如果傳入的屬性不是物件,則直接把該屬性轉換成輸出上的訪問器屬性,如果是物件的話,遞迴呼叫walk去深層遍歷。

//    將輸入轉換成Vue例項上的訪問器屬性
    Vue.prototype.convert =
        function (ins, key, value) {
            let _value = value;
            let that = this;
            Object.defineProperty(ins, key, {
                configurable: true,
                enumerable: true,
                get: function () {
                    return _value;
                },
                set: function (newVal) {
                    if (newVal === null || typeof newVal !== `object`) {
                        _value = newVal;
                        that.render(that.data, that.entry);
                    } else {
                        delete ins[key];
                        that.walk(ins[key], newVal);
                        that.render(that.data, that.entry);
                    }
                }
            })
        };複製程式碼

轉換方法convert就是把對應的鍵和值通過Object.defineProperty轉換成例項物件上訪問器屬性,使用一個變數_value來儲存get返回的值。其中配置屬性(configurable)要設定為true(預設為false),這樣需要需要重寫同名屬性的時候可以把它的值給刪除。set裡如果設定的值不是物件則直接改變閉包的_value,使用render方法來重新整理頁面。如果設定的值是物件的話要把現在的訪問器給刪除。使用walk來遍歷呼叫convert來設定新設定的值,然後重新整理頁面。

//    用來渲染頁面
    Vue.prototype.render = (function () {
        let domCache;
        return function (data, entry) {
            console.log(`render...`);
            domCache = domCache || entry.innerHTML;
            let domInnerHtml = domCache;
            let reg = /{{.*}}/g;
            let templateArr = [];
            let matchCache;
            let keyCache;
            let value;
            while (matchCache = reg.exec(domCache)) {
                templateArr.push(matchCache[0]);
            }
            templateArr.forEach(item => {
                keyCache = item.slice(2, -2).split(`.`);
                value = this.find(keyCache, data);
                if (value !== undefined && (typeof value !== `object` || value === null)) {
                    reg = new RegExp(`{{` + keyCache.join(`.`) + `}}`, `g`);
                    domInnerHtml = domInnerHtml.replace(reg, value);
                }
            });
            entry.innerHTML = domInnerHtml;
        }
    }());複製程式碼
//    查詢某個屬性在data中的值
    Vue.prototype.find = function (key, data) {
        for (let i = 0, len = key.length; i < len; i++) {
            if (data.hasOwnProperty(key[i])) {
                data = data[key[i]];
            } else {
                return undefined;
            }
        }
        return data;
    };複製程式碼

使用一個閉包儲存一個單例的domCache來儲存dom快取,防止{{}}被替換後,找不到雙向繫結的位置。不過也有個問題就是不能動態地改變雙向繫結的dom,不過我發現Vue也不了就釋然了……建立一個dom快取的副本,避免直接修改快取,使用正則找到雙花括號的位置,用正則的exec方法產生的陣列的第一個元素就是匹配到的元素,所有的匹配儲存到一個陣列裡。對匹配到的元素陣列遍歷。遍歷中用slice返回一個切掉{{和}}的副本再以.分割,得到每一層屬性名組成的陣列,通過find方法用這個陣列對data迭代判斷、操作得到最終的值,建立正則,把dom快取副本中屬性替換成對應的值。遍歷完後把修改過的dom快取副本賦值給真實的dom,完成渲染。


預覽
原始碼
求star、贊、關注?

相關文章