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

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

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

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

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

// 要實現(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

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

  • obj是待操作的對象仰挣;
  • key是對象中待操作的屬性名
  • descriptor是一個配置對象,其中有:
    configurable:配置總開關(guān)缠沈,默認(rèn)為false膘壶,只有當(dāng)其值為true時错蝴,才能動態(tài)的修改配置方法;
    enumerable:枚舉開關(guān)颓芭,默認(rèn)為false顷锰,只有當(dāng)其值為true時,該屬性才能被枚舉亡问;
    value:默認(rèn)為undefined官紫,即為本意obj[key] = value;
    writable:默認(rèn)為false,只有其值為true時州藕,屬性值才能被改寫万矾;
    set/get:為屬性提供getter和setter方法。
    </blockquote>

為了實現(xiàn)第一個需求慎框,我們需要為每一個屬性均提供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;
    //在對象中遍歷,只能用for..in..但是這個方法會將原型鏈上的屬性方法均會遍歷
    //因此用Object.hasOwnProperty進(jìn)行過濾后添,只保留自身對象上的
    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;
            //如果值為對象,那么還得給對象里的屬性綁定setter/getter
            if(typeof newVal == "object"){
                new Observer(newVal);
            }
            val = newVal;
            return val;
        }
    })
}

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

//發(fā)布訂閱模式壮不,一個中間層(包括一個訂閱接口,一個取消接口皱碘,一個發(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);  //實際上等于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);
};

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

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

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

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

此時健蕊,我們在第一個需求的基礎(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;
    //在對象中遍歷踢俄,只能用for..in..但是這個方法會將原型鏈上的屬性方法均會遍歷
    //因此用Object.hasOwnProperty進(jìn)行過濾缩功,只保留自身對象上的
    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ā)布訂閱模式都办,一個中間層(包括一個訂閱接口掂之,一個取消接口抗俄,一個發(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);  //實際上等于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
});


// 你需要實現(xiàn) $watch 這個 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)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市跟压,隨后出現(xiàn)的幾起案子胰蝠,更是在濱河造成了極大的恐慌,老刑警劉巖震蒋,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件茸塞,死亡現(xiàn)場離奇詭異,居然都是意外死亡查剖,警方通過查閱死者的電腦和手機钾虐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來笋庄,“玉大人效扫,你說我怎么就攤上這事≈鄙埃” “怎么了菌仁?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長静暂。 經(jīng)常有香客問我济丘,道長,這世上最難降的妖魔是什么洽蛀? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任摹迷,我火速辦了婚禮,結(jié)果婚禮上郊供,老公的妹妹穿的比我還像新娘泪掀。我一直安慰自己,他們只是感情好颂碘,可當(dāng)我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布异赫。 她就那樣靜靜地躺著,像睡著了一般头岔。 火紅的嫁衣襯著肌膚如雪塔拳。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天峡竣,我揣著相機與錄音靠抑,去河邊找鬼。 笑死适掰,一個胖子當(dāng)著我的面吹牛颂碧,可吹牛的內(nèi)容都是我干的荠列。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼载城,長吁一口氣:“原來是場噩夢啊……” “哼肌似!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起诉瓦,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤川队,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后睬澡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體固额,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年煞聪,在試婚紗的時候發(fā)現(xiàn)自己被綠了斗躏。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡昔脯,死狀恐怖啄糙,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情栅干,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布捐祠,位于F島的核電站碱鳞,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏踱蛀。R本人自食惡果不足惜窿给,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望率拒。 院中可真熱鬧崩泡,春花似錦、人聲如沸猬膨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽勃痴。三九已至谒所,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間沛申,已是汗流浹背劣领。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留铁材,地道東北人尖淘。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓奕锌,卻偏偏與公主長得像,于是被迫代替她去往敵國和親村生。 傳聞我的和親對象是個殘疾皇子惊暴,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,724評論 2 354

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