使用 ES6 Proxy 代理的 this 問題記錄

最近在項目裏使用了 Proxy,遇到一些問題記錄一下javascript

Proxy 簡介

簡單來講 Proxy 是對對象設置一個攔截,直接上代碼java

let obj = {
    attr: 1
};
// 對 obj 進行攔截
obj = new Proxy(obj, {
  get: function (target, key, receiver) {
    //若是 obj 有這個屬性,則直接返回
    if(key in target) {
        return target[key];
    } 
    //若是 obj 沒有這個屬性,則統一返回 '沒有這個值'
    return '沒有這個值';
  },
  set: function (target, key, value, receiver) {
    // 想設置對象屬性,直接返回(不讓設置)
    return true;
  }
});

// 使用
console.log(obj.attr); // 輸出 1
console.log(obj.abc); // 輸出 '沒有這個值', 

obj.attr = 2;//
console.log(obj.attr); // 輸出 1, 不能設置對象屬性

複製代碼

能夠進行的攔截類型不止 get, set,還有不少,例如:(來自阮一峯老師的書 ES6 Proxy):es6

  • set(target, propKey, value, receiver):攔截對象屬性的設置,好比proxy.foo = v或proxy['foo'] = v,返回一個布爾值。
  • has(target, propKey):攔截propKey in proxy的操做,返回一個布爾值。
  • deleteProperty(target, propKey):攔截delete proxy[propKey]的操做,返回一個布爾值。
  • ownKeys(target):攔截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循環,返回一個數組。該方法返回目標對象全部自身的屬性的屬性名,而Object.keys()的返回結果僅包括目標對象自身的可遍歷屬性。
  • getOwnPropertyDescriptor(target, propKey):攔截Object.getOwnPropertyDescriptor(proxy, propKey),返回屬性的描述對象。
  • defineProperty(target, propKey, propDesc):攔截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一個布爾值。
  • preventExtensions(target):攔截Object.preventExtensions(proxy),返回一個布爾值。
  • getPrototypeOf(target):攔截Object.getPrototypeOf(proxy),返回一個對象。
  • isExtensible(target):攔截Object.isExtensible(proxy),返回一個布爾值。
  • setPrototypeOf(target, proto):攔截Object.setPrototypeOf(proxy, proto),返回一個布爾值。若是目標對象是函數,那麼還有兩種額外操做能夠攔截。
  • apply(target, object, args):攔截 Proxy 實例做爲函數調用的操做,好比proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。 construct(target, args):攔截 Proxy 實例做爲構造函數調用的操做,好比new proxy(...args)。

如上這些都是介紹都是直接拷貝的 阮一峯老師的書 ES6 Proxychrome

遇到的問題

對原生的瀏覽器 HTMLElement 對象進行攔截

一、攔截時的 this 指向問題 阮老師書裏也有說起數組

在攔截方法裏,this 指向 Proxy 對象,因此在調用原對象的方法時,須要注意,直接看代碼瀏覽器

let div = document.querySelector('div'); // 隨便拿一個頁面的 div, 對他進行代理

// 第一種代理方法
let divProxy = new Proxy(div, {
  get: function (target, key, receiver) {
    // 訪問這個 div 的任何屬性,都直接返回
    return target[key];
  }
});
// 調用 div 的 querySelector 方法,拿他下邊的 a 標籤
console.log(divProxy.querySelector('a')); // chrome 上會報錯:Uncaught TypeError: Illegal invocation

複製代碼

如上的代碼,猜想由於 querySelector 方法內部有訪問 this 指向,致使報錯。修改成以下代碼,則能夠修復這個問題:babel

let div = document.querySelector('div'); // 隨便拿一個頁面的 div, 對他進行代理

// 第二種代理方法
let divProxy = new Proxy(div, {
  get: function (target, key, receiver) {
    if( !!target[key] && !!target[key].bind) {
        // 使用 bind 綁定 this 指向
        return target[key].bind(target);
    } else {
        return target[key];
    }
  }
});
console.log(divProxy.querySelector('a')); // <a href='https://t.tt'>來買呀</a>

複製代碼

另外還有一個問題,經過代理設置對象屬性時,也會有問題相同的,看代碼吧app

let div = document.querySelector('div');
let divProxy = new Proxy(div, {});
// 設置 div 的 innerHTML 屬性,設置對象屬性默認實際上是調用 `div.innerHTML.__defineSetter__` 方法
divProxy.innerHTML = '<a href="https://t.tt">來買呀</a>'; //chrome 報錯: Uncaught TypeError: Illegal invocation

複製代碼

直接上解決辦法函數

let div = document.querySelector('div'); 
let divProxy = new Proxy(div, {
    // 攔截全部的 set 行爲
    set: function (target, key, value, receiver) {
        if(key in target) {
            target[key] = value; // 直接調用原對象屬性進行設置(target[key])
        }
        return true;
    }
});
divProxy.innerHTML = '<a href="https://t.tt">來買呀</a>';
複製代碼

結尾

通常 Proxy 會和 Reflect 一塊兒使用,本文不作介紹了,直接上阮老師的書ui

JavaScript 的語法愈來愈規範,以前的一些坑,新的標準規範也開始慢慢填,加上babel的普及使用,咱們能夠多多擁抱新語法、特性,簡潔代碼,愉悅本身。

相關文章
相關標籤/搜索