重學(xué)JS(十)—— 使用觀察者模式實(shí)現(xiàn)雙向綁定

老掉牙的文章了碧注,不過為了加深上一篇對(duì)觀察者模式的理解蠢笋,所以來自己實(shí)現(xiàn)一個(gè)簡單的vue雙向綁定。

目標(biāo)

給一個(gè)input做個(gè)雙向綁定的功能

<div id="ele">
  <input v-model="test"/>
  {{test}}
</div>
<script src="./src/observer.js"></script>
<script src="./src/watcher.js"></script>
<script src="./src/compile.js"></script>
<script src="./src/mvvm.js"></script>
<script>
  const vm = new MVVM({
    el:'ele',
    data(){
      return {
        test:''
      }
    }
  })
</script>

思路

input => 數(shù)據(jù) : 給input加個(gè)事件填硕,變化的時(shí)候改變數(shù)據(jù)即可擂错。
數(shù)據(jù) => input :通過defineProperty設(shè)置get和set屬性來劫持?jǐn)?shù)據(jù),觸發(fā)視圖的更新粱锐。

開始

1疙挺、給對(duì)象所有的鍵值都用defineProperty設(shè)置get,set怜浅。
function observe(data){
    if(typeof data !== "object"){ //如果不是對(duì)象
        return;
    }
    Object.keys(data).forEach(key => { //遍歷對(duì)象鍵值
        defineReactive(data,key,data[key]); 
    });
    
}
function defineReactive(data,key,val){
    observe(val);
    Object.defineProperty(data,key,{
        enumerable: true,
        configurable: true,
        get(){
            return val;
        },
        set(newval){
            val = newval;
        }
    })
}

這樣铐然,一旦修改數(shù)據(jù)都能在set函數(shù)中監(jiān)聽到。

2、實(shí)現(xiàn)MVVM構(gòu)造函數(shù)锦爵。

在調(diào)用MVVM構(gòu)造函數(shù)的時(shí)候舱殿,需要把data里面所有的鍵值都綁定上get和set。然后編譯模板险掀。

class MVVM{
    constructor(options){
        this._options = options;
        let data = this._data = options.data();
        observe(data);  //給數(shù)據(jù)的所有鍵值加上get set
        let dom = document.getElementById(options.el);
        new Compile(dom ,this); //編譯模板了
    }
}
3沪袭、實(shí)現(xiàn)模板編譯

模版編譯就是遍歷節(jié)點(diǎn),尋找具有v-model屬性的元素節(jié)點(diǎn)樟氢,以及{{}}這種格式的文本節(jié)點(diǎn)(簡化了冈绊,vue有很多指令都需要進(jìn)行判斷)。

class Compile{
    constructor(el,vm){
        this._el = el;
        this._compileElement(el);
    }

    _compileElement(el){  //遍歷節(jié)點(diǎn)
        let childs = el.childNodes;
        Array.from(childs).forEach(node => {
            if (node.childNodes && node.childNodes.length) {
                this._compileElement(node);
            }else{
                this._compile(node);
            }

        })
    }

    _compile(node){
        if(node.nodeType == 3){ //文本節(jié)點(diǎn)
            let reg = /\{\{(.*)\}\}/;
            let text = node.textContent;
            if(reg.test(text)){
                //如果這個(gè)元素是{{}}這種格式
            }
        }else if(node.nodeType == 1){ //元素節(jié)點(diǎn)
            let nodeAttr = node.attributes;
            Array.from(nodeAttr).forEach(attr => {
                if(attr == "v-model"){
                    //如果這個(gè)元素有v-model屬性埠啃,那么得做點(diǎn)事情了
                }   

            })  
        }
        
    }
}

現(xiàn)在停下來思考死宣,如果查到了某個(gè)元素的屬性有v-model,我們該做什么碴开。
一個(gè)數(shù)據(jù)變化毅该,所有它關(guān)聯(lián)的dom元素都需要更新。咦潦牛,這不就是觀察者模式做的事嗎眶掌!觀察者會(huì)被添加到目標(biāo)中,目標(biāo)一通知巴碗,所有的觀察者都會(huì)更新朴爬。所以,查到元素有v-model后(或者{{}})橡淆,就需要?jiǎng)?chuàng)建一個(gè)觀察者召噩,添加到目標(biāo)(數(shù)據(jù))中。

4逸爵、實(shí)現(xiàn)觀察者

觀察者需要實(shí)現(xiàn)一個(gè)update方法具滴。

class Watcher{
    constructor(vm,exp,cb){  //初始化的時(shí)候把對(duì)象和鍵值傳進(jìn)來
        this._cb = cb;
        this._vm = vm;
        this._exp = exp;   //保存鍵值
        this._value = vm[exp]; //隱藏開關(guān),這句代碼會(huì)發(fā)生什么痊银?
    }

    update(){
        let value = this._vm[_exp];
        if(value != this._value){
            this._value = value;
            this._cb.call(this.vm,value);
        }
    }
}

vm[exp] 就會(huì)觸發(fā)get抵蚊,這點(diǎn)很重要。

5溯革、實(shí)現(xiàn)目標(biāo)

觀察者是被添加到目標(biāo)上的,所以得寫個(gè)目標(biāo)的構(gòu)造函數(shù)

class Dep{ //目標(biāo)
    constructor(){
        this.subs = [];
    }

    add(watcher){
        this.subs.push(watcher)
    }

    notify(){
        this.subs.forEach(sub => {
            sub.update();
        })
    }
}
6谷醉、準(zhǔn)備就緒致稀,將一切串聯(lián)起來

(1)每次給key值添加get,set的時(shí)候都要?jiǎng)?chuàng)建一個(gè)Dep(目標(biāo))。
(2)每次模板編譯的時(shí)候俱尼,遇到v-model或者{{}}就創(chuàng)建一個(gè)觀察者添加到Dep抖单。同時(shí)將data里的值賦給node。input還需要綁定一個(gè)input事件,輸入時(shí)改變對(duì)象里的值矛绘。
(3)準(zhǔn)備一個(gè)全局變量耍休,利用key值的get屬性添加watcher。
完成版:
watcher:

var uId = 0;
class Watcher{
    constructor(vm,exp,cb){  //初始化的時(shí)候把對(duì)象和鍵值傳進(jìn)來
        this._cb = cb;
        this._vm = vm;
        this._exp = exp;   //保存鍵值
        this._uid = uId;
        uId++; //每個(gè)觀察者配個(gè)ID货矮,防止重復(fù)添加
        Target = this;
        this._value = vm[exp]; //看到?jīng)]羊精,這里觸發(fā)getter了
        Target = null; //用完就刪
    }

    update(){
        let value = this._vm[this._exp];
        if(value != this._value){
            this._value = value;
            this._cb.call(this.vm,value);
        }
    }
}

obeserve:

function defineReactive(data,key,val){
    observe(val);
    let dep = new Dep();
    Object.defineProperty(data,key,{
        enumerable: true,
        configurable: true,
        get(){
            Target && dep.add(Target); //添加觀察者了
            return val;
        },
        set(newval){
            val = newval;
            dep.notify();  //通知所有觀察者去更新
        }
    })
}

watcher:

_compile(node){
        if(node.nodeType == 3){ //文本節(jié)點(diǎn)
            let reg = /\{\{(.*)\}\}/;
            let text = node.textContent;
            if(reg.test(text)){
                //如果這個(gè)元素是{{}}這種格式
                let key = RegExp.$1;
                node.textContent =  this._vm[key];
                new Watcher(this._vm,key,val=>{
                    node.textContent =  val;
                })
            }
        }else if(node.nodeType == 1){ //元素節(jié)點(diǎn)
            let nodeAttr = node.attributes;
            Array.from(nodeAttr).forEach(attr => {
                if(attr.nodeName == "v-model"){
                    node.value =  this._vm[attr.nodeValue]; //初始化賦值
                    //如果這個(gè)元素有v-model屬性,那么得做點(diǎn)事情了
                    node.addEventListener('input',()=>{
                        this._vm[attr.nodeValue] = node.value;  
                    })
                    new Watcher(this._vm,attr.nodeValue,val =>{
                        node.value =  val;
                    })
                }   

            })  
        }
    }

一個(gè)很簡單雙向綁定囚玫,很多指令我都沒去解析喧锦,看看理解下觀察者模式就好了。附上Github

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末抓督,一起剝皮案震驚了整個(gè)濱河市燃少,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌铃在,老刑警劉巖阵具,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異定铜,居然都是意外死亡阳液,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門宿稀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來趁舀,“玉大人,你說我怎么就攤上這事祝沸“耄” “怎么了?”我有些...
    開封第一講書人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵罩锐,是天一觀的道長奉狈。 經(jīng)常有香客問我,道長涩惑,這世上最難降的妖魔是什么仁期? 我笑而不...
    開封第一講書人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮竭恬,結(jié)果婚禮上跛蛋,老公的妹妹穿的比我還像新娘。我一直安慰自己痊硕,他們只是感情好赊级,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著岔绸,像睡著了一般理逊。 火紅的嫁衣襯著肌膚如雪橡伞。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評(píng)論 1 308
  • 那天晋被,我揣著相機(jī)與錄音兑徘,去河邊找鬼。 笑死羡洛,一個(gè)胖子當(dāng)著我的面吹牛挂脑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播翘县,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼最域,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了锈麸?” 一聲冷哼從身側(cè)響起镀脂,我...
    開封第一講書人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎忘伞,沒想到半個(gè)月后薄翅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡氓奈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年翘魄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片舀奶。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡暑竟,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出育勺,到底是詐尸還是另有隱情但荤,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布涧至,位于F島的核電站腹躁,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏南蓬。R本人自食惡果不足惜纺非,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望赘方。 院中可真熱鬧烧颖,春花似錦、人聲如沸窄陡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽泳梆。三九已至鳖悠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間优妙,已是汗流浹背乘综。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留套硼,地道東北人卡辰。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像邪意,于是被迫代替她去往敵國和親九妈。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理雾鬼,服務(wù)發(fā)現(xiàn)萌朱,斷路器,智...
    卡卡羅2017閱讀 134,693評(píng)論 18 139
  • 前言 在之前面試中策菜,有被問到這個(gè)問題晶疼,雖然了解過是劫持Object.defineProperty方法,但是其細(xì)節(jié)并...
    Aleph_Zheng閱讀 1,074評(píng)論 0 5
  • 第十七天進(jìn)程和線程-------- 1.進(jìn)程: 就是正在運(yùn)行的程序又憨,分配內(nèi)存讓應(yīng)用程序能夠運(yùn)行翠霍。 Windows系...
    枇杷樹8824閱讀 1,007評(píng)論 0 0
  • 王密亮搭車路上行,忽聞湖里雪花漂過蠢莺。森林河里深十七尺寒匙,不及中國夢的故事里送我學(xué)習(xí)知識(shí)。
    王密亮閱讀 105評(píng)論 0 0