天天看點

ES6學習系列——Proxy

Proxy 用于修改某些操作的預設行為,等于是在語言層面做出了修改,也就是對程式設計語言進行改動。具體來說,Proxy就是一種機制,用來攔截外界對目标對象的通路,可以對這些通路進行過濾或者改寫,是以Proxy更像是目标對象的代理器。

1、ES6 原生提供Proxy構造函數,可以用來生成proxy執行個體:

(1)執行個體

let proxy = new Proxy(target, handler);

接收兩個參數:

target 是要代理的目标對象;

handler 也是一個對象,用來定義攔截的具體行為;如果攔截具有多個操作,就可以這樣定義handler {fn, ….}

(2)Proxy 攔截操作彙總,共13個:
  • get(target, propKey, receiver)

    :攔截對象屬性的讀取,比如

    proxy.foo

  • set(target, propKey, value, receiver)

    :攔截對象屬性的設定,

    比如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)

(3)Proxy常用攔截方法詳解(暫時就用到這兩個,過後再補):

get ( ): 如果某個屬性不可寫或者不可配置,則該屬性不能被通路,set()也同理;

如上所述,這個get() 方法用來攔截對目标對象的屬性通路;還是上執行個體吧:

let idiot= {
    name: "Troyes",
    age: 
};
let proxy = new Proxy(idiot, {
    get (target, property) {

        //我天,有個return怎麼就用不了三元操作符?
        if (property in target){
            return target[property];
        }else{
            throw new ReferenceError("the property you want to get:"+ property +" does not exist!");
        }

        //老衲有妙招:就是有點煩,flag無論真假,都是return出去,容我以後再來想想能不能優化
        //return (property in target) ? (target[property]) : ( "the property you want to get:"+ property +" does not exist!");
    }
});
console.log(proxy.name, proxy.age); //Troyes 
console.log(proxy.job); //ReferenceError: the property you want to get:job does not exist! 
           

set ( ):

這個方法用于攔截對某個屬性的指派操作,廢話不都說,上執行個體:

let checker = {
    set (target, prop, val) {
        //驗證設定的特定的屬性值符不符合要求
        if (prop=== 'age') {
            if (Number.isInteger(val)) {
                if (val>) {
                    throw new RangeError("Are you f**king kiding me?");
                }   
            }else{
                throw new TypeError("The age is not a integer.");
            }

        }

        //無要求的屬性值,符合要求的age都直接存起來
        target[prop] = val;
    }
};

let Person = new Proxy({}, checker);

Person.age = ;
console.log(Person.age); //35
Person.age = "123d"; //TypeError: The age is not a integer.
Person.age = ; //RangeError: Are you f**king kiding me?
Person.name = "troyes";
console.log(Person.name); //troyes
           
(4)關于 This

用了

Proxy

之後,proxy代理的 this 并非指向目标對象,而是指向自身proxy。是以這裡就會有坑:某些原生對象的 this 指向如果不對,是無法通路到他的内部屬性的;(解決方法下面有講到)

let a = {
    toConsole () {
        if (this === proxy) {
            console.log("the 'this' of a change its pointing to proxy.");
        }else if (this === a) {
            console.log("waiting to chang its pointing.");
        }
    }
};

let proxy = null;
a.toConsole(); //waiting to chang its pointing.
proxy = new Proxy( a, {});
a.toConsole(); //waiting to chang its pointing. 在a中,a的this沒變,還是a;
proxy.toConsole (); //the 'this' of a change its pointing to proxy. 但是在proxy中,this卻已經指向了proxy;
           

有些原生對象的内部屬性的this不對就會報錯;那麼知道坑在這裡,怎麼解決呢?

答案就是通過綁定this,寫 handler 這個參數的時候,通過 bind(target) 直接把this 綁定到原生對象 target上 ;

let target = new Date();
let handler = {
    get (target, prop) {
        if (prop === 'getTime') {
            //直接把this綁定到target上
            return target.getTime.bind(target);
        }
        return Reflect.get(target, prop);   
    }

};
let proxy = new Proxy(target, handler);
let nowTime = proxy.getTime();
console.log(nowTime); //1536074540607
//加上這句,直接報錯
proxy.getDay(); //TypeError: this is not a Date object. 
           

寫到這裡,索性再來說說

bind(obj)

這個方法吧;

正如上面說的,調用這個方法就會直接把 this 直接綁定到 obj 上;

var module = {
  x: ,
  getX: function () {
    return this.x;
  }
}

var unboundGetX = module.getX;
console.log(unboundGetX()); // The function gets invoked at the global scope, the this is pointing to window, so window.x is undefined
// expected output: undefined

var boundGetX = unboundGetX.bind(module);
console.log(boundGetX());
//bind the this to module
// expected output: