ES6黑科技實踐--proxy,reflect

開始之前

* reflect兼容性*

proxy兼容性

上面兩圖分別是截止當前,proxy和reflect的瀏覽器支持程度。可以看出proxy和reflect的支持已經相當好了,新一點的主流瀏覽器都支持了(除了IE)。

所以還是相當有必要玩耍一下的。

proxy

簡單介紹

其實es6出來了這麼久了,在實際的項目中也都使用es6編程。對於某些特殊的屬性,如proxy,雖然用的不多,但我們或多或少也瞭解到proxy的用法。詳細的介紹這裏不贅述,可以移步MDN查看es6介紹,當然這裏也有一篇大神的es6使用大全,值得深究。

總之,用一句話總結就是:改變了過去對象監聽的複雜操作,使用proxy可以用一種更優雅的方式實現外部對對象的訪問。

es5的實現

那麼或許問題來了,在沒有proxy之前,我們是怎麼樣實現對對象的監聽呢?

其實在es5中,我們可以使用 Object.definePropertyObject.defineProperty來實現對對象的監聽。利用es5對象的getter 和 setter方法,可以實現簡單的文件監聽,使用方法如下:

// 如何實現一個自存檔對象。 當設置temperature 屬性時,archive 數組會獲取日誌條目。
function Archiver() {
  var temperature = null;
  var archive = [];

  Object.defineProperty(this, 'temperature', {
    get: function() {
      console.log('get!');
      return temperature;
    },
    set: function(value) {
      temperature = value;
      archive.push({ val: temperature });
    }
  });

  this.getArchive = function() { return archive; };
}

var arc = new Archiver();
arc.temperature; // 'get!'
arc.temperature = 11;
arc.temperature = 13;
arc.getArchive(); // [{ val: 11 }, { val: 13 }]

目前支持雙向綁定的Vue中的實現就是這種方法。但是這種方法不太好的地方就是對於數組之類的對象,類似修改數組的length,直接用索引設置元素如items[0] = {},以及數組的push,pop等變異方法是無法觸發setter的。針對這些,vue中的實現是在Object和Array的原型添加了定製方法來處理這些特殊操作,可以實現上述要求。

第三方庫的實現

請移步:

reflect

怎麼理解reflect

reflect 是es6新增的一個全局對象。顧名思義,反射,類似於Java裏面的反射機制。在Java裏面,反射是個很頭疼的概念。簡單理解爲:通過反射,我們可以在運行時獲得程序或程序集中每一個類型的成員和成員的信息。對於Java來說,程序中一般的對象的類型都是在編譯期就確定下來的,而Java反射機制可以動態地創建對象並調用其屬性,這樣的對象的類型在編譯期是未知的。所以我們可以通過反射機制直接創建對象,即使這個對象的類型在編譯期是未知的。

而對於js來說自然是有些不同了。畢竟js不需要編譯,同時萬物皆對象的特性,這些都讓理解js的reflect起來相當簡單。

對於JS中的reflect,我們就可以理解爲:有這麼一個全局對象,上面直接掛載了對象的某些特殊方法,這些方法可以通過Reflect.apply這種形式來使用,當然所有方法都是可以在 Object 的原型鏈中找到的。是不是相當簡單。

使用reflect的好處

引自知乎專欄:ES6 Reflect

  1. Reflect上面的一些方法並不是專門爲對象設計的,比如Reflect.apply方法,它的參數是一個函數,如果使用Object.apply(func)會讓人感覺很奇怪。
  2. 用一個單一的全局對象去存儲這些方法,能夠保持其它的JavaScript代碼的整潔、乾淨。不然的話,這些方法可能是全局的,或者要通過原型來調用。
  3. 將一些命令式的操作如delete,in等使用函數來替代,這樣做的目的是爲了讓代碼更加好維護,更容易向下兼容;也避免出現更多的保留字。

常見的方法

Reflect.apply
Reflect.construct
Reflect.defineProperty
Reflect.deleteProperty
Reflect.enumerate // 廢棄的
Reflect.get
Reflect.getOwnPropertyDescriptor
Reflect.getPrototypeOf
Reflect.has
Reflect.isExtensible
Reflect.ownKeys
Reflect.preventExtensions
Reflect.set
Reflect.setPrototypeOf

具體用法當然是看:MDN: reflect介紹

一個使用proxy和reflect實現監聽對象的小例子

栗子來源(非本人):https://github.com/sindresorhus/on-change

on-change是一個可以監聽對象或者數組內部變化的小工具,主要使用proxy來實現。以下是核心代碼:

// onChange 即要進行的監聽操作
module.exports = (object, onChange) => {
    const handler = {
        get(target, property, receiver) {
            try {
                return new Proxy(target[property], handler);
            } catch (err) {
                return Reflect.get(target, property, receiver);
            }
        },
        defineProperty(target, property, descriptor) {
            onChange();
            return Reflect.defineProperty(target, property, descriptor);
        },
        deleteProperty(target, property) {
            onChange();
            return Reflect.deleteProperty(target, property);
        }
    };

    return new Proxy(object, handler);
};

代碼很精簡,但是也是有必要研究下,是一位大大牛 sindresorhus 的作品。

其實一共有三個方法,get defineProperty defineProperty,上面代碼可以對數組進行操作就是因爲用了proxy,具體的實現在get方法,每一層返回一個proxy,需要注意的是在監聽操作這裏依然使用的是 es5的 defineProperty 方法。具體的可以自己研究下,還是很有可玩性的。

參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章