Vue雙向數據綁定原理及實現

對於Vue.js技術棧,咱們的第一想法有可能就是容易上手,對於新手比較友好。確實如此,筆者剛剛入手的時候,以爲比較容易,並且在使用的過程當中,也感受到了它的強大。html

最近在準備面試,只知道Vue.js的使用是遠遠不夠的,因此開始剖析Vue.js的源碼。下面一步一步講解其原理以及實現。vue

先給出源碼的github地址。node

1、理解Vue.js

官方介紹:Vue.js是一套用於構建用戶界面的漸進式框架。那麼「漸進式」要如何理解呢?git

Vue的核心的功能,是一個視圖模板引擎,但這不是說Vue就不能成爲一個框架。以下圖所示,這裏包含了Vue的全部部件,在聲明式渲染(視圖模板引擎)的基礎上,咱們能夠經過添加組件系統、客戶端路由、大規模狀態管理來構建一個完整的框架。更重要的是,這些功能相互獨立,你能夠在覈心功能的基礎上任意選用其餘的部件,不必定要所有整合在一塊兒。能夠看到,所說的「漸進式」,其實就是Vue的使用方式,同時也體現了Vue的設計的理念github

漸進式表明的含義是:沒有多作職責以外的事。面試

Vue.js只提供了 vue-cli 生態中最核心的組件系統雙向數據綁定(也叫數據驅動)vue-cli

2、雙向數據綁定原理及實現

注意:由於該功能的實現用到了ES6的語法以及一些日常不常使用的知識,第三部分主要講解了用到的知識點,能夠先了解了基礎知識知識以後再來看這一部分的實現。編程

咱們都知道Vue.js的兩個核心就是 組件系統數據驅動(雙向數據綁定),因此接下來咱們就 雙向數據綁定進行講解以及實現。數組

如上圖所示:總體實現分爲四步:

  1. 實現一個Compile,對指令進行解析,初始化視圖,而且訂閱數據的變動,綁定好更新函數
  2. 實現一個Observer,對數據進行劫持,通知數據的變化
  3. 實現一個Watcher,將其做爲以上二者的一箇中介點,在接受數據變動的同時,讓Dep添加當前的Watcher,並及時通知視圖進行update
  4. 實現MVVM,整合以上三者,做爲一個入口函數

爲了更好地理解實現過程,貼出一張更加詳細的流程圖:瀏覽器

下面的講解主要分爲三部分:模板編譯(Compiler)數據劫持(Observer)觀察者(Watcher)

咱們都知道,在使用Vue.js的時候咱們都須要 new Vue({}),此時的Vue即爲一個類,大(花)括號裏面傳遞的內容即爲Vue的屬性和方法。要想實現雙向數據綁定,須要的最基本的元素即爲 eldata,有了可編譯的模板和數據,咱們才能夠進行接下來的模板編譯以及數據劫持,最終經過觀察者來實時監測數據的變化進而來不斷的更新視圖。因此 Vue類的做用能夠理解爲一個橋樑,將模板編譯,數據劫持鏈接起來。

由於下面的代碼以及講解內容都爲筆者本身實現的功能爲例,因此Vue類更名爲了MVVM,基本功能是同樣的(下面的代碼不完整,主要目的是爲了講解編寫的主要流程和主要功能)。

1.模板編譯(Compiler)

class Compile {
    //vm-->MVVM中傳入的第二個參數就是MVVM的實例,即new MVVM()
    constructor(el, vm) {
        //傳入的多是 #app或者document.getElementById('app'),因此須要進行判斷
        this.el = this.isElementNode(el) ? el : document.querySelector(el); 
        this.vm = vm;
        //防止用戶輸入的既不是「#el」字符串也不是document節點
        if (this.el) {
            //若是這個元素可以獲取到,咱們纔開始編譯
            
            //1.先把真實的DOM移入到內存中(優化性能) -->使用節點碎片 fragment
            let fragment = this.nodeToFragment(this.el);
            
            //2.編譯=>提取想要的元素節點(v-model)和文本節點{{}}
            this.compile(fragment)
            
            //3.把編譯好的fragment在放回到頁面中
            this.el.appendChild(fragment)

        }
    }
複製代碼

在判斷擁有可編譯模板以後,接下來就要分別進行下面三步:

1.1 將真實DOM移入到內存中

直接操做DOM節點是很是損耗性能的,更況且對於一個真實的頁面或者一個項目,DOM層會有不少節點以及嵌套的節點,因此,若是咱們直接操做DOM,可想而知性能會變得不好。在這裏咱們要藉助 fragment 節點碎片,來減小由於直接大量的操做DOM而形成的性能問題。(這個過程能夠簡單的理解爲將DOM節點都移入到內存中,在內存中對DOM節點進行一系列的操做,這樣就會提升性能)

nodeToFragment(el) { //須要將el中的內容所有放入到內存中
        //文檔碎片,不是真正的DOM,是內存中的節點
        let fragment = document.createDocumentFragment();
        let firstChild;
        while (firstChild = el.firstChild) {
            //將el中的真實節點一個一個的移入到文檔碎片中(el.firstChild指文檔中的第一個節點,這一個節點裏面可能嵌套不少個節點,可是都不要緊,都會一次取走)
            fragment.appendChild(firstChild);
        }
        return fragment; // 內存中的節點
    }
複製代碼

上面的一段代碼就是將DOM節點移入到內存中的過程,在執行完上面一段代碼以後,打開瀏覽器的控制檯你會發現以前的節點都已經消息了(存入到了內存中)。

1.2 編譯=>(提取到須要編譯的元素節點和文本節點)

compile(fragment) {
        //須要遞歸
        let childNodes = fragment.childNodes; //只拿到第一層(父級),拿不到嵌套層的
        Array.from(childNodes).forEach(node => {
            if (this.isElementNode(node)) {
                //這裏的須要編譯元素
                this.compileElement(node);
                //是元素節點,還須要繼續深刻的檢查(若是是元素節點,有可能節點裏面會嵌套節點,因此要使用遞歸)
                this.compile(node) //由於外層是箭頭函數,因此this始終指向Compile實例
            } else {
                //是文本節點
                //這裏須要編譯文本
                this.compileText(node)
            }
        })

    }
    compileElement(node) {
        //編譯帶v-model、v-text等的(取節點的屬性)
        let attrs = node.attributes; //取出當前節點的屬性
        Array.from(attrs).forEach(attr => {
            //判斷屬性名字是否是包含v-
            let attrName = attr.name;
            if (this.isDirective(attrName)) {
                //取到對應的值(即從data中取到message(示例)),放到節點中
                let expr = attr.value;
                let [, type] = attrName.split('-') //解構賦值
                //node  this.vm.$data expr  //這裏可能有v-model或v-text  還有可能有v-html(這裏只處理前兩種)
                CompileUtil[type](node, this.vm, expr)
            }
        })
    }
    compileText(node) {
        //編譯帶{{}}
        let expr = node.textContent; //取文本中的內容
        let reg = /\{\{([^}]+)\}\}/g;
        if (reg.test(expr)) {
            //node this.vm.$data expr
            CompileUtil['text'](node, this.vm, expr)
        }
    }
複製代碼

上面的三個函數最終的結果是拿到了最終須要編譯的元素節點,最後就是要將傳入的 data中對應的數據顯示在模板上。

//文本更新
        textUpdater(node, value) {
            node.textContent = value
        },
        //輸入框更新
        modelUpdater(node, value) {
            node.value = value
        }
複製代碼

1.3 編譯好的fragment在放回到頁面中

將數據顯示在節點上以後,咱們發現頁面上並無顯示任何數據,並且元素節點也不存在,那是由於上面的一系列操做都是咱們在內存中進行的,最後咱們須要將編譯好的 fragment放回到頁面中。

this.el.appendChild(fragment)
複製代碼

至此,模板編譯部分就結束了了,這時候咱們就會發現咱們在data中定義的數據已經徹底渲染在頁面上了。

接下來,咱們繼續實現 數據的劫持

2.數據劫持(Observer)

顧名思義,數據劫持就是對 data中的每個屬性值進行監測,只要數據變化了,就要作出相應的事情(這裏就是更新視圖)。話很少說,先貼代碼,在說明其中的幾個注意點

class Observer {
    constructor(data) {
        this.observer(data)
    }
    observer(data) {
        //要對這個data數據原有的屬性改爲set和get的形式
        if (!data || typeof data !== 'object') { //若是數據不存在或者不是對象
            return;
        }
        //要將數據一一劫持,先獲取到data的key和value
        Object.keys(data).forEach(key => { //該方法是將對象先轉換成數組,再循環
            //劫持(定義一個函數,數據響應式)
            this.defineReactive(data, key, data[key]);
            //深度遞歸劫持,這裏的遞歸只會爲初始的data中的數據進行劫持(添加set和get方法),若是在defineReactive函數中使用set新增長則不會進行劫持
            this.observer(data[key]);
        })
    }
    //定義響應式
    defineReactive(obj, key, value) {
        //在獲取某個值的時候,能夠在獲取或更改值的時候,作一些處理
        let that = this;
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: true,
            get() { //當取值時,調用的方法
                return value;
            },
            set(newValue) { //當給data屬性中設置值的時候,更改獲取的屬性的值
                if (newValue !== value) {
                    console.log(this, 'this'); //這個this指向的是被修改的值
                    //可是這裏的this不是Observer的實例,因此須要在最初保存一下當前this指向
                    that.observer(newValue); //若是是對象繼續劫持
                    value = newValue;
                }
            }
        })
    }

    /**
     * 以上就實現了數據劫持
     */
}
複製代碼

數據劫持部分比較簡單,主要使用了 Object.defineProperty(),下面列出一個須要注意的地方:

  • 在對data數據中的原有屬性改成 getset以前,須要對data進行判斷,排除不是對象和數據不存在的狀況
  • 由於 data中的數據多是多層嵌套的對象,因此要進行深層遞歸,可是這裏的遞歸只會爲data中初始的數據進行劫持,對於新添加的則不會。
  • 基於上面一條的缺陷,因此咱們須要在爲數據添加set方法時,對數據也進行劫持(由於此時的this指向的是被修改的值,因此須要在方法最初保存一下當前的this值)

核心的模板編譯和數據劫持已經完成,兩個部分也均可以實現本身的職能,可是如何將二者關聯起來,達到最終雙向綁定的效果呢?

下面就是結合二者的 Watcher的主場了!!!

3.觀察者 (Watcher)

建立Watcher觀察者,用新值和老值進行比對,若是發生變化了,就調用更新方法,進行視圖的更新。

class Watcher {
    constructor(vm, expr, cb) {
        this.vm = vm;
        this.expr = expr;
        this.cb = cb;
        //先獲取一下老的值
        this.value = this.get();
    }
    getVal(vm, expr) { //獲取實例上對應的數據
        expr = expr.split('.');
        return expr.reduce((prev, next) => { //vm.$data.a....
            return prev[next];
        }, vm.$data)
    }
    get() {
        Dep.target = this; //將當前watcher實例放入到tartget中
        let value = this.getVal(this.vm, this.expr);
        Dep.target = null;
        return value;
    }
    //對外暴露的方法
    update() {
        let newValue = this.getVal(this.vm, this.expr);
        let oldValue = this.value;
        if (newValue !== oldValue) {
            this.cb(newValue); //對應watch的callback
        }
    }
}

複製代碼

在這裏還要插入一個知識,發佈-訂閱模式:

//observer.js
/**
 * 發佈訂閱
 */
class Dep {
    constructor() {
        //訂閱的數組
        this.subs = [];
    }
    //添加訂閱者
    addSub(watcher) {
        this.subs.push(watcher);
    }
    //通知
    notify() {
        this.subs.forEach(watcher => {
            watcher.update()
        })
    }
}
複製代碼

發佈訂閱在這裏的做用:由於 Watcher是來觀察數據變化的,即訂閱者。由於一個數據可能在模板的多處使用,因此一個數據會有多個監測者。便可以理解爲對於一個數據有多個訂閱者,那麼當一個數據變化時,就能夠一次通知即可實現全部訂閱者都知道這個消息的結果。(也就是一個數據變化,模板中使用這個數據的值都發生了改變)

上面的一段話是我的現階段的理解,若是有誤,但願能夠提出來,共同改進和努力~~~

結合上面的兩個功能,就能夠將整個數據雙向綁定聯絡起來:

當在模板編譯中 建立 Watcher實例時,這行代碼 Dep.target = this; //將當前watcher實例放入到tartget中 就會將監聽這個數據變化的訂閱者防盜訂閱者數組中,注意,由於Dep中沒有target這個屬性,因此在使用完以後,記得釋放該沒有必要的內存空間 Dep.target = null;,經過這一步,咱們就先將全部訂閱者都放入到了訂閱者的數組中。

// compile.js
 //這裏應該加一個監控,數據變化了,應該調用這個watch的callback
        new Watcher(vm, expr, (newValue) => {
            //當值變化後,會調用cb將新值傳遞過來()
            updateFn && updateFn(node, this.getVal(vm, expr))
        })
複製代碼
//observer.js
    //定義響應式
    defineReactive(obj, key, value) {
        //在獲取某個值的時候,能夠在獲取或更改值的時候,作一些處理
        let that = this;
        console.log(that, this);
        
        let dep = new Dep(); //每一個變化的數據都會對應一個數組,這個數組是存放全部更新的操做
        
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: true,
            get() { //當取值時,調用的方法
            
                Dep.target && dep.addSub(Dep.target);
                
                return value;
            },
            set(newValue) { //當給data屬性中設置值的時候,更改獲取的屬性的值
                if (newValue !== value) {
                    console.log(this, 'this'); //這個this指向的是被修改的值
                    //可是這裏的this不是Observer的實例,因此須要在最初保存一下當前this指向
                    that.observer(newValue); //若是是對象繼續劫持
                    value = newValue;
                    dep.notify(); //通知全部人數據更新了
                }
            }
        })
    }
複製代碼

在數據劫持的部分定義一個數組Dep.target && dep.addSub(Dep.target);,存放須要更新的訂閱者。

在獲取值的時候,將這些訂閱者都放到上面定義的數組中,Dep.target && dep.addSub(Dep.target);

在改變值的時候,就會調用 dep.notify(); //通知全部人數據更新了,間接調用 watcher.update()來更新數據。

到此爲止,雙向數據綁定已經基本實現,下面還有兩點簡單的內容。

爲輸入框添加點擊事件

//爲節點添加點擊事件
        node.addEventListener('input', e => {
            let newValue = e.target.value;
            this.setVal(vm, expr, newValue);
        })
複製代碼

添加代理

當咱們訪問實例上的數據時,咱們都要經過 this.$data.message才能訪問到,由於咱們的數據是 $data裏面的,若是咱們想要實現 this.message就能訪問到數據,這時候就須要使用一層代理。

proxyData(data) {
        Object.keys(data).forEach(key => {
            Object.defineProperty(this, key, {
                get() {
                    return data[key]
                },
                set(newValue) {
                    data[key] = newValue
                }
            })
        })
    }
複製代碼

最終效果

3、準備知識

剖析Vue.js的源碼過程當中發現,Vue.js的底層使用了不少咱們平時代碼實現不怎麼用到的知識,這裏做爲一個入門簡單的羅列一下。

1. ES6中類及其定義

問題一:傳統JS類的定義

JS定義類的的傳統方法:是經過構造函數,定義並生成新對象,prototype屬性使您有能力向對象添加屬性和方法。

案例:

//Person.js
function Person(x,y){    
    this.x = x;   
    this.y = y;
}

Person.prototype.toString = function (){    
    return (this.x + "的年齡是" +this.y+"歲");
}

export {Person};

//index.js
import {Person} from './Person';
let person = new Person('張三',12);
console.log(person.toString()); /張三的年齡是12歲
複製代碼

問題二:ES6中類的定義

ES6引入了Class(類)這個概念,做爲對象的模板,經過class關鍵字,能夠定義類。 基本上,ES6的Class能夠看做只是一個語法糖,它的絕大部分功能,ES5均可以作到,新的Class寫法只是讓對象原型的寫法更加清晰、更像面向對象編程的語法而已。 上面的代碼用ES6的「類」改寫,就是下面這樣:

//Person.js
class Person{    
    // 構造    
    constructor(x,y){        
        this.x = x;            
        this.y = y;    
    }
    toString(){        
        return (this.x + "的年齡是" +this.y+"歲");    
    }
}
export {Person};

//index.js
import {Person} from './Person';
let person = new Person('張三',12);
console.log(person.toString()); /張三的年齡是12歲
複製代碼

面代碼定義了一個「Class類」,能夠看到裏面有一個constructor方法,這就是構造方法,而this關鍵字則表明實例對象。 也就是說,ES5的構造函數Person,對應ES6的Person類的構造方法。 Person類除了構造方法,還定義了一個toString方法。

注意,定義「類」的方法的時候,前面不須要加上function這個關鍵字,直接把函數定義放進去了就能夠了。 另外,方法之間不須要逗號分隔,加了會報錯。

一個類必須有constructor方法,若是沒有顯式定義,一個默認的constructor方法會被添加。因此即便你沒有添加構造函數,也是有默認的構造函數的。

2. JS中的文檔碎片

在瀏覽器中,咱們一般用innerHTML()或者appendChild()向頁面中插入DOM節點,例如:

for(var i=0;i<5;i++){

    var op = document.createElement("span"); 

    var oText = document.createTextNode(i); 

    op.appendChild(oText); 

    document.body.appendChild(op); 
}
複製代碼

可是,若是當咱們要向document中添加大量數據時(好比1w條),若是像上面的代碼同樣,逐條添加節點,這個過程就可能會十分緩慢。 固然,你也能夠建個新的節點,好比說div,先將oP添加到div上,而後再將div添加到body中,但這樣要在body中多添加一個<div></div>.但文檔碎片不會產生這種節點。

var oDiv = document.createElement("div"); 

for(var i=0;i<10000;i++){ 

    var op = document.createElement("span"); 

    var oText = document.createTextNode(i); 

    op.appendChild(oText); 

    oDiv.appendChild(op);  
} 
document.body.appendChild(oDiv);
複製代碼

爲了解決這個問題,JS引入了createDocumentFragment()方法,它的做用是建立一個文檔碎片,把要插入的新節點先附加在它上面,而後再一次性添加到document中。 代碼以下:

//先建立文檔碎片

var oFragmeng = document.createDocumentFragment(); 

for(var i=0;i<10000;i++){ 

    var op = document.createElement("span"); 

    var oText = document.createTextNode(i); 

    op.appendChild(oText); 

    //先附加在文檔碎片中

    oFragmeng.appendChild(op);  

} 
//最後一次性添加到document中
document.body.appendChild(oFragmeng);
複製代碼

3. 解構賦值

按照必定的模式從數組或者對象中取值,對變量進行賦值的過程稱爲解構。

上面的代碼表示,能夠從數組中取值,按照位置的對應關係對變量賦值。

4. Array.from與Array.reduce

一、ES6中的 Array.from()方法 Array.from方法

用於將兩類對象轉爲真正的數組: 相似數組的對象(array-like object) 和 可遍歷(iterable)的對象(包括 ES6 新增的數據結構SetMap)。

下面是一個相似數組的對象,Array.from將它轉爲真正的數組:

let arrayLike = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
};
複製代碼
// ES5的寫法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']
複製代碼
// ES6的寫法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
複製代碼

實際應用中,常見的相似數組的對象是 DOM 操做返回的 NodeList 集合,以及函數內部的arguments對象。Array.from均可以將它們轉爲真正的數組。

/ NodeList對象
let ps = document.querySelectorAll('p');
Array.from(ps).filter(p => {
  return p.textContent.length > 100;
});

// arguments對象
function foo() {
  var args = Array.from(arguments);
  // ...
}
複製代碼

上面代碼中,querySelectorAll方法返回的是一個相似數組的對象,能夠將這個對象轉爲真正的數組,再使用filter方法。

更多參考

二、ES5中的 Array.reduce() 方法

reduce() 方法接收一個函數做爲累加器(accumulator),數組中的每一個值(從左到右)開始合併,最終爲一個值。

參數 描述
callback 執行數組中每一個值的函數,包含四個參數
previousValue 上一次調用回調返回的值,或者是提供的初始值(initialValue)
currentValue 數組中當前被處理的元素
index 當前元素在數組中的索引
array 調用 reduce 的數組
initialValue 做爲第一次調用 callback 的第一個參數。

詳細描述

  • reduce 爲數組中的每個元素依次執行回調函數,不包括數組中被刪除或從未被賦值的元素,接受四個參數:初始值(或者上一次回調函數的返回值),當前元素值,當前索引,調用 reduce 的數組。

  • 回調函數第一次執行時,previousValuecurrentValue 能夠是一個值,若是 initialValue在調用 reduce 時被提供,那麼第一個 previousValue等於initialValue,而且currentValue等於數組中的第一個值;若是initialValue 未被提供,那麼previousValue 等於數組中的第一個值,currentValue等於數組中的第二個值。

  • 若是數組爲空而且沒有提供initialValue, 會拋出TypeError 。若是數組僅有一個元素(不管位置如何)而且沒有提供initialValue, 或者有提供initialValue可是數組爲空,那麼此惟一值將被返回而且callback不會被執行。

使用示例:

var total = [0, 1, 2, 3].reduce(function(a, b) {
    return a + b;
});
console.log(total);//6

var total = [0, 1, 2, 3].reduce(function(a, b) {
    return a + b;
},10);
console.log(total);//16
複製代碼

5.遞歸的使用

6.Obj.keys()與Obj.defineProperty()

問題一:Obj.keys()的使用

Object.keys()方法返回一個由一個給定對象的自身可枚舉屬性組成的數組。

使用示例:

var person = {
       firstName: "aaaaaa",
       lastName: "bbbbbb",
       others: "ccccc"
};
Object.keys(person).forEach(function(data) {
       console.log('person', data, ':', person[data]);
 });
//console.log:
//person firstName : aaaaaa
//person lastName : bbbbbb
//person others : ccccc
複製代碼

問題二:Obj.defineProperty()的使用

Object.defineProperty()方法會直接在一個對象上定義一個新屬性,或者修改一個對象的現有屬性, 並返回這個對象。

語法:

Object.defineProperty(obj, prop, descriptor)
複製代碼

參數說明:

obj:必需。目標對象 
prop:必需。需定義或修改的屬性的名字
descriptor:必需。目標屬性所擁有的特性
複製代碼

返回值:

傳入函數的對象。即第一個參數obj
複製代碼

給對象的屬性添加特性描述,目前提供兩種形式:數據描述和存取器描述。

1、數據描述

當修改或定義對象的某個屬性的時候,給這個屬性添加一些特性:

var obj = {
    test:"hello"
}
//對象已有的屬性添加特性描述
Object.defineProperty(obj,"test",{
    configurable:true | false,
    enumerable:true | false,
    value:任意類型的值,
    writable:true | false
});
//對象新添加的屬性的特性描述
Object.defineProperty(obj,"newKey",{
    configurable:true | false,
    enumerable:true | false,
    value:任意類型的值,
    writable:true | false
});
複製代碼

設置的特性總結:

value: 設置屬性的值
writable: 值是否能夠重寫。true | false
enumerable: 目標屬性是否能夠被枚舉。true | false
configurable: 目標屬性是否能夠被刪除或是否能夠再次修改特性 true | false
複製代碼

注意:

  • 除了能夠給新定義的屬性設置特性,也能夠給已有的屬性設置特性

  • 一旦使用Object.defineProperty給對象添加屬性,那麼若是不設置屬性的特性,那麼configurable、enumerable、writable這些值都爲默認的false。

2、存取器描述

當使用存取器描述屬性的特性的時候,容許設置如下特性屬性:

var obj = {};
Object.defineProperty(obj,"newKey",{
    get:function (){} | undefined,
    set:function (value){} | undefined
    configurable: true | false
    enumerable: true | false
});
複製代碼

注意:當使用了getter或setter方法,不容許使用writable和value這兩個屬性

getter/setter 當設置或獲取對象的某個屬性的值的時候,能夠提供getter/setter方法。

  • getter 是一種得到屬性值的方法

  • setter是一種設置屬性值的方法。 在特性中使用get/set屬性來定義對應的方法。

var obj = {};
var initValue = 'hello';
Object.defineProperty(obj,"newKey",{
    get:function (){
        //當獲取值的時候觸發的函數
        return initValue;    
    },
    set:function (value){
        //當設置值的時候觸發的函數,設置的新值經過參數value拿到
        initValue = value;
    }
});
//獲取值
console.log( obj.newKey );  //hello
//設置值
obj.newKey = 'change value';
console.log( obj.newKey ); //change value
複製代碼

注意:get或set不是必須成對出現,任寫其一就能夠。若是不設置方法,則get和set的默認值爲undefined。configurable和enumerable同上面的用法。

7.發佈-訂閱模式

問題一:發佈-訂閱模式,又稱爲 觀察者模式

觀察者模式概念解讀

觀察者模式又叫發佈訂閱模式(Publish/Subscribe),它定義了一種一對多的關係,讓多個觀察者對象同時監聽某一個主題對象,這個主題對象的狀態發生變化時就會通知全部的觀察者對象,使得它們可以自動更新本身。

觀察者模式做用和注意事項

模式做用:

  • 一、支持簡單的廣播通訊,自動通知全部已經訂閱過的對象。

  • 二、頁面載入後目標對象很容易與觀察者存在一種動態關聯,增長了靈活性

  • 三、目標對象與觀察者之間的抽象耦合關係可以單獨擴展以及重用。

注意事項:

監聽要在觸發以前。

到此爲止,雙向數據綁定的原理以及實現的思路已經基本完成,同時也講解了功能實現中須要的基本知識,但願讀者能夠從中收穫知識,也歡迎提出不一樣的意見,虛心採納~~~

相關文章
相關標籤/搜索