BaiDuIFE ------ vue動(dòng)態(tài)數(shù)據(jù)綁定實(shí)現(xiàn)過程

感謝百度前端技術(shù)學(xué)院帶領(lǐng)我進(jìn)步!謹(jǐn)以此文,記錄我的學(xué)習(xí)過程~
動(dòng)態(tài)數(shù)據(jù)綁定

Vue的初學(xué)者一定對(duì)于數(shù)據(jù)的動(dòng)態(tài)綁定并不陌生,從最簡單的需求開始一步步理解其實(shí)現(xiàn)原理,以及相關(guān)涉及到的知識(shí)點(diǎn)。

let app1 = new Observer({
  name: 'youngwind',
  age: 25
});

let app2 = new Observer({
  university: 'bupt',
  major: 'computer'
});

// 要實(shí)現(xiàn)的結(jié)果如下:
app1.data.name // 你訪問了 name
app.data.age = 100;  // 你設(shè)置了 age,新的值為100
app2.data.university // 你訪問了 university
app2.data.major = 'science'  // 你設(shè)置了 major,新的值為 science

實(shí)際上這需要用到ES5中的Object.prototype.defineProperty(參見文檔)這個(gè)方法,簡單敘述一下其參數(shù):
<blockquote>
Object.defineProperty(obj,key,descriptor)

  • obj是待操作的對(duì)象;
  • key是對(duì)象中待操作的屬性名
  • descriptor是一個(gè)配置對(duì)象,其中有:
    configurable:配置總開關(guān),默認(rèn)為false,只有當(dāng)其值為true時(shí),才能動(dòng)態(tài)的修改配置方法;
    enumerable:枚舉開關(guān),默認(rèn)為false,只有當(dāng)其值為true時(shí),該屬性才能被枚舉;
    value:默認(rèn)為undefined,即為本意obj[key] = value;
    writable:默認(rèn)為false,只有其值為true時(shí),屬性值才能被改寫;
    set/get:為屬性提供getter和setter方法。
    </blockquote>

為了實(shí)現(xiàn)第一個(gè)需求,我們需要為每一個(gè)屬性均提供getter/setter:

function Observer(data) {
    this.data = data;
    this.makeObserver(data);
}
Observer.prototype.makeObserver = function(data){
   if(typeof data !== "object"){
        throw "please input object!"
    }
    let val;
    //在對(duì)象中遍歷,只能用for..in..但是這個(gè)方法會(huì)將原型鏈上的屬性方法均會(huì)遍歷
    //因此用Object.hasOwnProperty進(jìn)行過濾,只保留自身對(duì)象上的
    for(let key in data){
        if(data.hasOwnProperty(key)){
            val = data[key];
            //如果還是引用類型,則迭代直至所有的屬性均綁定了get/set
            if(typeof val === "object"){
                new Observer(val)
            }
            this.convert(key,val);
        }
    }
}
Observer.prototype.convert = function(){
    Object.defineProperty(this.data,key,{
        enumerable:true,
        configurable:true,
        get:function () {
            console.log("你訪問了" + key);
            return val;
        },
        set:function (newVal,func) {
            console.log("你設(shè)置了" + key + "新的值為" + newVal);
            if(newVal == val) return;
            //如果值為對(duì)象,那么還得給對(duì)象里的屬性綁定setter/getter
            if(typeof newVal == "object"){
                new Observer(newVal);
            }
            val = newVal;
            return val;
        }
    })
}

綁定了getter,setter后,只是進(jìn)行了數(shù)據(jù)獲取/修改后的反饋,并沒有涉及到觸發(fā)數(shù)據(jù)改動(dòng)后的回調(diào)。我們可以用事件觸發(fā)的思路,也就是利用發(fā)布訂閱(觀察者)模式來進(jìn)行函數(shù)回調(diào)。
簡單來說:就是單獨(dú)創(chuàng)建一個(gè)中間層,用以統(tǒng)一管理事件,該中間層給出兩個(gè)接口,一個(gè)用于訂閱事件,一個(gè)用于發(fā)布事件;
一個(gè)簡單的實(shí)現(xiàn):

//發(fā)布訂閱模式,一個(gè)中間層(包括一個(gè)訂閱接口,一個(gè)取消接口,一個(gè)發(fā)布接口)
function PubSub(){
    this.handlers = {};
}
PubSub.prototype.on = function (eventType,handler) {
    if (!(eventType in this.handlers)){
        this.handlers[eventType] = [];
    }
    this.handlers[eventType].push(handler);
    return this;
}
PubSub.prototype.emit = function (eventType) {
    if(!this.handlers[eventType]) return;
    var handlerArgs = [].slice.call(arguments,1);  //刨除eventType,保留其他參數(shù)
    for(var i = 0; i < this.handlers[eventType].length;i++){
        this.handlers[eventType][i].apply(this,handlerArgs);  //實(shí)際上等于func(..rest);
    }
    return this;
}
PubSub.prototype.off = function (eventType) {
    if(!(eventType in this.handlers)) return;
    delete this.handlers[eventType];
    return this;
}
Observer.prototype.$watch = function(attr, callback){
    this.eventBus.on(attr, callback);
};

第二個(gè)需求變?yōu)椋?/p>

 let app1 = new Observer({
         name: 'youngwind',
         age: 25
 });

 // 你需要實(shí)現(xiàn) $watch 這個(gè) API
 app1.$watch('age', function(age) {
         console.log(`我的年紀(jì)變了,現(xiàn)在已經(jīng)是:${age}歲了`)
 });

 app1.data.age = 100; // 輸出:'我的年紀(jì)變了,現(xiàn)在已經(jīng)是100歲了'

此時(shí),我們?cè)诘谝粋€(gè)需求的基礎(chǔ)上,利用觀察者模式的思想添加中間層:

function Observer(data) {
    this.data = data;
    this.makeObserver(data);
    this.eventBus = new PubSub();
}
//該方法就是給屬性綁get/set
Observer.prototype.makeObserver = function (data) {
    if(typeof data !== "object"){
        throw "please input object!"
    }
    let val;
    //在對(duì)象中遍歷,只能用for..in..但是這個(gè)方法會(huì)將原型鏈上的屬性方法均會(huì)遍歷
    //因此用Object.hasOwnProperty進(jìn)行過濾,只保留自身對(duì)象上的
    for(let key in data){
        if(data.hasOwnProperty(key)){
            val = data[key];
            //如果還是引用類型,則迭代直至所有的屬性均綁定了get/set
            if(typeof val === "object"){
                new Observer(val)
            }
            this.convert(key,val);
        }
    }
};
Observer.prototype.convert = function (key,val) {
    var that = this;
    Object.defineProperty(this.data,key,{
        enumerable:true,
        configurable:true,
        get:function () {
            console.log("你訪問了" + key);
            return val;
        },
        set:function (newVal,func) {
            console.log("你設(shè)置了" + key + "新的值為" + newVal);
            if(newVal == val) return;
            if(typeof newVal == "object"){
                new Observer(newVal);
            }
            that.eventBus.emit(key,newVal);    //觸發(fā)訂閱
            val = newVal;
            return val;
        }
    })
};


//發(fā)布訂閱模式,一個(gè)中間層(包括一個(gè)訂閱接口,一個(gè)取消接口,一個(gè)發(fā)布接口)
function PubSub(){
    this.handlers = {};
}
PubSub.prototype.on = function (eventType,handler) {
    if (!(eventType in this.handlers)){
        this.handlers[eventType] = [];
    }
    this.handlers[eventType].push(handler);
    return this;
}
PubSub.prototype.emit = function (eventType) {
    if(!this.handlers[eventType]) return;
    var handlerArgs = [].slice.call(arguments,1);  //刨除eventType,保留其他參數(shù)
    for(var i = 0; i < this.handlers[eventType].length;i++){
        this.handlers[eventType][i].apply(this,handlerArgs);  //實(shí)際上等于func(..rest);
    }
    return this;
}
PubSub.prototype.off = function (eventType) {
    if(!(eventType in this.handlers)) return;
    delete this.handlers[eventType];
    return this;
}
Observer.prototype.$watch = function(attr, callback){
    this.eventBus.on(attr, callback);
};


let app1 = new Observer({
    name: 'youngwind',
    age: 25
});


// 你需要實(shí)現(xiàn) $watch 這個(gè) API
app1.$watch('age', function(age) {
    console.log(`我的年紀(jì)變了,現(xiàn)在已經(jīng)是:${age}歲了`)
});

app1.data.age = 100; // 輸出:'我的年紀(jì)變了,現(xiàn)在已經(jīng)是100歲了'
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容