JS的平凡之路--模仿Vue寫個簡單的對象監聽

本篇實現的僅僅是對象監聽,不包含數組。關於數組的下一篇會講。vue

1、用什麼來監聽對象?

  Vue給了咱們很好的回答,那就是defineProperty。defineProperty方法有如下幾個參數:react

  • obj: 你要設置屬性的對象;
  • propName: 你設置的屬性的名稱;
  • descriptor: 描述符對象;

  這個descriptor對象能夠設置的屬性就比較多了:git

  • value: 設置屬性的值;
  • writable: 默認爲false,若是該值爲false,則不能再修改屬性的值;
  • enumerable: 默認爲false,若是該值爲false,則不能被枚舉;
  • configurable: 默認爲false,若是該值爲false,那麼descriptor不能再被修改,也不能使用delete;
  • get與set:存取器方法,默認爲undefined。

  其餘的descriptor對象理解應該不難,看了MDN上的教程,應該會發現對於set和get方法的使用,老是會在外部聲明一個變量來維護值的變化,是否是很變扭,而後就會有人嘗試用this來調用屬性,這時就會報執行棧溢出,由於它遞歸的執行本身,致使棧溢出。github

  這裏咱們能夠經過set和get內部維護的'_'開頭的私有變量來解決:api

const obj = {};
            Object.defineProperty(obj,'name', {
                get () {
                    console.log('讀取name的值爲 => ' + this._name);
                    return this._name;
                },
                set (newValue) {
                    this._name = newValue;
                    console.log('設置name的值爲 => ' + this._name);
                }
            })
            obj.name = 'xiaoming'; // '設置name的值爲 => xiaoming'
            obj.name; // '讀取name的值爲 => xiaoming'複製代碼

  兩種方法看着喜愛用吧。數組

2、實現對象的監聽

  首先咱們得有一個對象: dom

const obj = {
                a: 1,
                b: {
                    c: 2,
                    d: 3
                }
            };複製代碼

  接下來咱們要將它變成一個響應式對象,讓它的取值與賦值操做多在咱們的監聽下,首先咱們要經過一個Observer來包裝咱們須要響應化的對象:函數

function Observer(value) {
                /** * 構造函數防止沒有使用'new'關鍵字建立 */
                if (!(this instanceof Observer)) {
                    return new Observer(value);
                }
                this.value = value;
                // 這一篇僅僅討論對象的監聽
                if (!Array.isArray(value)) {
                    this.walk(value);
                }
            }

            Observer.prototype.walk = function (obj) {
                const keys = Object.keys(obj);
                for (let i = 0, max = keys.length; i < max; i++) {
                    const key = keys[i];
                    //將對象響應化
                    defineReactive(obj, key, obj[key]);
                }
            }複製代碼

  接下來核心就是採用defineProperty方法來監聽set和get方法,可是這裏咱們要注意的是採用遞歸建立Observer對象的方式,解決對象嵌套的問題:學習

function defineReactive(obj, key, val) {
                //獲取對象的描述器對象
                const property = Object.getOwnPropertyDescriptor(obj, key);
                //這裏上面強調了configurable屬性的重要性
                if (property && property.configurable === false) {
                    return;
                }

                const setter = property && property.set;
                const getter = property && property.get;

                //解決對象嵌套的問題
                observe(val);

                Object.defineProperty(obj, key, {
                    enumerable: true,
                    configurable: true,
                    get () {
                        const value = getter ?  getter.call(obj) : val;
                        log('讀取',obj,key,value);
                        return value;
                    },
                    set (newValue) {
                        const value = getter ? getter.call(obj) : val;
                        if (setter) {
                            setter.call(obj, newValue);
                        } else {
                            val = newValue;
                        }
                        log('設置',obj,key,value);
                    }
                })
            }

            function observe(obj) {
                if (!isObject(obj)) {
                    return;
                }
                return new Observer(obj);
            }複製代碼

  這裏基本上對象的監聽已經完成了,下面來試試:ui

Observer(obj);
        obj.b.d = 10;複製代碼

控制檯輸出結果
控制檯輸出結果

  可是這裏還存在一個問題,由於咱們還有這種操做:

obj.f = 10;複製代碼

  這時obj.f再也不被咱們監聽,對於這種狀況在Vue中咱們要經過Vue.set方法設置,這裏咱們也能夠寫了set方法:

function set(target, key, val) {
                if(!target.hasOwnProperty(key)) {
                    target[key] = val;
                    defineReactive(target, key, val);
                }
            }複製代碼

  這裏是個簡易的set方法,主要思想就是使新屬性響應化。

3、總結

  經過源碼的學習,使你在使用Vue的一些api更加駕輕就熟。

  PS:源碼用的flow.js,看起來有點費勁,可是我應該會堅持下去。(下一篇會講數組的監聽)

參考資料
Vue core部分observer
熊建剛博客

相關文章
相關標籤/搜索