1. 程式人生 > >ES6黑科技實踐--proxy,reflect

ES6黑科技實踐--proxy,reflect

開始之前

上面兩圖分別是截止當前,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的好處

  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

一個使用proxy和reflect實現監聽物件的小例子

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);
};

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

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

參考