對proxy的理解及其常見用法

  雖然之前看過proxy的相關文章,可是最近在看到某爲大神用proxy實現了單例及數據雙向綁定,因此決定再次好好的來了解一下proxy的用法,謹以此文來記錄我對它的理解及用法。es6

一、什麼是Proxy?

  Proxy 用於修改某些操做的默認行爲,能夠理解成,在目標對象以前架設一層「攔截」,外界對該對象的訪問,都必須先經過這層攔截,所以提供了一種機制,能夠對外界的訪問進行過濾和改寫。   換句話說就是——Proxy對象就是可讓你去對JavaScript中的一切合法對象的基本操做進行自定義,而後用你自定義的操做去覆蓋其對象的基本操做。說白了就是重寫其其所屬類或構造函數中的基本操做數組

  其基本語法是:安全

let proxy=new Proxy(target,handler)
複製代碼
  • target——是你要對其基本操做進行自定義的對象,它能夠是JavaScript中的任何合法對象。
  • handler——是你要自定義操做方法的一個對象.
  • proxy——是一個被代理後的新對象,它擁有target的一切屬性和方法.只不過其行爲和結果是在handler中自定義的。在必定程度上能夠當作是對target的引用。

下面咱們來看一個例子app

let obj={a:1};
    let proxy=new Proxy(obj,{
        get(target,proxy){
            return 34;
        }
    });
    proxy.b=50;
    proxy.c=60;
    obj.d=70;
    console.log(obj);
    console.log(proxy);
    console.log(proxy.d);
    console.log(obj.d);
    //輸出結果以下所示:
    //{a: 1, b: 50, c: 60, d: 70}
    //Proxy {a: 1, b: 50, c: 60, d: 70}
    //35
    //70
複製代碼

  結果分析: 要想使得代理起做用,必須針對Proxy實例(上面是proxy對象)進行操做,而不是針對目標對象(上例是空對象)進行操做。並且對代理對象的操做也會影響到目標對象,防止也同樣。在handler空對象的狀況下,對target和proxy對象的操做是同樣的。函數

  再來看下面的這個例子post

let obj={a:1};
    let proxy=new Proxy(obj,{
        get(target,proxy){
            return 35;
        }
    })
    let object=Object.create(proxy);
    //等效於 let object__proto__=proxy;
    object.b=30;
    console.log(obj);// {a: 1}
    console.log(proxy);//Proxy {a: 1}
    console.log(object);//{b: 30}
    console.log(object.b);//30
    console.log(object.a);//35
複製代碼

  結果分析: 當咱們訪問對象的某個屬性時,最早會訪問對象自己,若是有該屬性,則直接返回,若是沒有則訪問其原型,若是仍是沒有,再訪問原型的原型,一直沿着原型鏈向上訪問,直到訪問到或者到達原型鏈終點(Object.prototype__proto__)爲止。有則返回該屬性的值,沒有則返回undefined。
  上面的代碼中object對象中有b屬性,因此直接返回該屬性的值,可是object對象中沒有a屬性,因此會去訪問object的原型,此時object的原型是proxy對象,因此object.a實際上至關於proxy.a,因此返回的是35。ui

二、Proxy 支持的攔截操做

  proxy一共支持13中攔截類型,基本上是對於Object的方法進行攔截。 我的以爲proxy攔截的好處主要有:
一、經過對某一操做進行攔截,能實現本身想要的效果,並且這種攔截屬於一種不公開的操做,具有很好的安全性。
二、經過對proxy對象的操做,來達到對目標對象的操做,很好的隱藏了目標對象,下降目標對象的曝光度,實現了對目標對象的保護效果。
三、相比較Object.defineProperty()而言,proxy功能更強大,不只能攔截對象的屬性,還能對整個對象,如數組和函數進行攔截。並且proxy的攔截種類更多。
this

proxy主要有如下攔截方法。spa

  • get(target, propKey,receiver): 攔截某個屬性的讀取操做,能夠接受三個參數,依次爲目標對象、屬性名和 proxy 實例自己(嚴格地說,表明原始的讀操做所在的那個對象),其中最後一個參數可選,通常狀況下指向的是proxy對象,可是若是proxy做爲其餘對象的原型時,則指向讀取該屬性的對象了。
      運用:get()方法能夠被繼承、get()方法能夠實現鏈式操做prototype

    const proxy = new Proxy({}, {
        get: function(target, property, receiver) {
          return receiver;
        }
      });
      console.log(proxy.a===proxy);//true
      const d = Object.create(proxy);//被繼承
      console.log(d.a === d) // true    
    複製代碼

    注意: 若是一個屬性的特性configurable和writable都爲false時,對該屬性設置get代理會報錯。及屬性值不可被刪除、不可被修改、不可被讀取,全部使用代理的get方法去讀取屬性值會報錯。

    const target = Object.defineProperties({}, {
          foo: {
              value: 123,
              writable: false,
              configurable: false
          },
      });
      const handler = {
          get(target, propKey) {
              return 'abc';
          }
      };
      const proxy = new Proxy(target, handler);
      console.log(proxy.foo);
      //'get' on proxy: property 'foo' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected '123' but got 'abc')
    複製代碼
  • set(target, propKey, value,receiver): 用來攔截某個屬性的賦值操做,能夠接受四個參數,依次爲目標對象、屬性名、屬性值和 Proxy 實例自己,其中最後一個參數可選。返回一個布爾值。
    運用: 利用set方法,能夠數據綁定,即每當對象發生變化時,會自動更新 DOM。結合get和set方法,就能夠定義私有屬性。
    注意: 若是目標對象自身的某個屬性,不可寫且不可配置(即configurable和writable都爲false),那麼set方法將不起做用。

    "use strict"
      let obj={};
      Object.defineProperty(obj,"name",{
          configurable:false,
          writable:false,
          value:"james"
      })
      let proxy=new Proxy(obj,{
          set(target, propKey, value,receiver){
              target[propKey="tang";
          }
      })
      console.log(obj.name);//james
    複製代碼

    注意: 嚴格模式下,set代理若是沒有返回true,就會報錯。好比沒有return語句或者return false;return;都會報錯

    "use strict"
      let obj={name:"james"};
      let proxy=new Proxy(obj,{
          set(target, propKey, value,receiver){
              target[propKey]="tang";
          }
      })
      proxy.test="test";
      //'set' on proxy: trap returned falsish for property 'test'
    複製代碼
  • has(target, propKey): 用來攔截HasProperty操做,用來判斷對象是否具備某個屬性。典型的操做就是in運算符。has方法能夠接受兩個參數,分別是目標對象、需查詢的屬性名。返回一個布爾值。就算你自定義返回的不是布爾值,也會轉換布爾值。
    注意: 一、若是某個屬性的configurable被設置爲false或者整個對象被設置爲禁止擴展的話。使用in運算符時,被has方法攔截時,必須返回target[key]所對應的值,不然會報錯。
    二、has攔截的不是hasProperty()方法,也不會攔截has...in操做。

    let obj={
          a:1,
          b:2
      }
       Object.defineProperty(obj,"c",{
          configurable:false,
          writable:false,
          value:"123",
      })
      Object.preventExtensions(obj);
      let proxy=new Proxy(obj,{
          has(target,key){
              if(key==="a"){
                  return target[key];
              }
              if(key==="b"){
                  return false;
              }else{
                  return false;
              }
          }
      })
      console.log("c" in proxy);//true
      console.log("a" in proxy);//true
      console.log("d" in proxy);//false
      //console.log("b" in proxy);//trap returned falsish for property 'b' but the proxy target is not extensible
      console.log(proxy.hasProperty("a"))// proxy.hasProperty is not a function
      for(key in proxy){
          console.log(key);//a b (c由於writable屬性被設置爲false,故讀取不到)
      }
    複製代碼
  • deleteProperty(target, propKey): 用於攔截delete操做。返回一個布爾值。
      該方法主要是攔截delete操做,並在deleteProperty執行真正想作的操做,同時對proxy的屬性執行delete操做,並不會真正將該屬性刪除。 不能夠刪除目標對象自身的不可配置(configurable)的屬性,不然報錯。

    let obj={
          a:1,
          b:2
      }
      Object.defineProperty(obj,"c",{
          configurable:false,
          value:"123"
      })
      let proxy=new Proxy(obj,{
          deleteProperty(target,key){
              if(key==="a"){
                  return false;
              }
              return true;
          }
      })
      delete proxy.a;
      delete proxy.b;
      console.log(proxy.a);//1
      console.log(proxy.b);//2
      console.log(obj);//{a:1,b:2}
      delete proxy.c;//'deleteProperty' on proxy: trap returned truish for property 'c' which is non-configurable in the proxy target
    複製代碼
  • ownKeys(target): 攔截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循環等操做。該方法返回目標對象全部自身的屬性的屬性名。
    注意:一、使用Object.keys方法時,有三類屬性會被ownKeys方法自動過濾 a、目標對象上不存在的屬性 b、屬性名爲 Symbol 值 c、不可遍歷(enumerable)的屬性
    二、ownKeys()的返回必須是一個數組,否則會報錯。且返回的數組成員必須是字符串或者Symbol值(便可覺得對象屬性的值)。
    三、obj對象的某屬性若是不可配置的,這時ownKeys方法返回的數組之中,必須包含該屬性,不然會報錯。 四、若是目標對象是不可擴展的(non-extensible),這時ownKeys方法返回的數組之中,必須包含原對象中全部可被返回的屬性(如Symbol屬性、不可枚舉屬性以及不存在的屬性都不能被返回,),且不能包含多餘的屬性,不然報錯。

    let obj={
          a:1,
          b:2
          Symbol.for("c"):4
      }
      Object.defineProperty(obj,"d",{
          enumerable:false
      })
      //片斷1
      let proxy=new Proxy(obj,{
          ownKeys(target){
             return true
          }
      });
      Object.keys(proxy);//報錯CreateListFromArrayLike called on non-object
      //片斷2
      let proxy=new Proxy(obj,{
          ownKeys(target){
             return Object.keys(target)
          }
      });
      Object.keys(proxy);//a,b
      //片斷3
      let proxy=new Proxy(obj,{
          ownKeys(target){
             return ["test","name","a",{}];//報錯
          }
      });
      Object.keys(proxy);//a 只會返回對象中存在的,且可枚舉的,非symbol屬性
    複製代碼
  • getOwnPropertyDescriptor(target, propKey): 攔截Object.getOwnPropertyDescriptor(proxy, propKey),返回屬性的描述對象。

  • defineProperty(target, propKey, propDesc): 攔截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs)以及set操做,返回一個布爾值。該方法的做用在於攔截這些操做,而後自定義操做,被攔截的操做並不能生效

    let obj = {
          a: 1,
          b: 2
      }
      let proxy = new Proxy(obj, {
          defineProperty(target, key,descriptor) {
              return true;
          }
      });
      Object.defineProperty(proxy,"c",{
          value:123
      })
      Object.defineProperty(proxy,"d",{
          value:"234"
      })
      console.log(obj);//{a:1,b:2}
      console.log(proxy);proxy{a:1,b:2}
    複製代碼
  • preventExtensions(target): 攔截Object.preventExtensions(proxy),返回一個布爾值。該方法必須返回一個布爾值,不然會被自動轉爲布爾值。該方法有一個強制,只有目標對象不可擴展時,才能返回true,不然會報錯

  • getPrototypeOf(target): 攔截獲取對象原型的操做。具體來講,攔截下面這些操做。
    注意: 一、該方法必須返回一個對象或者null,不然會報錯。
    二、另外,若是目標對象不可擴展(non-extensible), getPrototypeOf方法必須返回目標對象的原型對象。

    Object.prototype.isPrototypeOf()
      Object.getPrototypeOf()
      Reflect.getPrototypeOf()
      instanceof
      
      let obj = {
          a: 1,
          b: 2
      }
      
      let proxy = new Proxy(obj, {
          getPrototypeOf(target){
              console.log("proto");
              return null;
          }
      });
      Object.getPrototypeOf(proxy);//proto
      Object.prototype.isPrototypeOf(proxy);//proto
      proxy instanceof Object;//proto
      Object.preventExtensions(obj);
      Object.getPrototypeOf(proxy);//getPrototypeOf' on proxy: proxy target is non-extensible but the trap did not return its actual prototype
    複製代碼
  • isExtensible(target): 攔截Object.isExtensible(proxy),返回一個布爾值。該方法只能返回布爾值,跟前面那些只能返回布爾值方法同樣,不然返回值會被自動轉爲布爾值。同時它的返回值必須與目標對象的isExtensible屬性保持一致,不然就會拋出錯誤。

  • setPrototypeOf(target, proto): 攔截Object.setPrototypeOf(proxy, proto),返回一個布爾值。若是目標對象是函數,那麼還有兩種額外操做能夠攔截。

  • apply(target, object, args): 攔截函數的調用、以及call、apply和bind操做,好比proxy(...args)、proxy.call(object, ...args)、proxy.apply(...),proxy.bind(...)()。

    function foo(a,b){
          return a+b;
      }
      let proxy=new Proxy(foo,{
          apply(target, ctx, args){
              let num=1;
              for(let i=0;i<args.length;i++){
                  num*=args[i];
              }
              //return target(...args);
              return num;
          }
      })
      console.log(proxy(10,20));//200
      let obj={
           a:10,
           b:30
       }
      console.log(proxy.call(obj,10,20));// 240 apply中的ctx爲obj
      console.log(proxy.call(window,10,20));// 200 apply中的ctx爲window
      console.log(proxy.call(null,10,20));//200 apply中的ctx爲null 
      console.log(proxy.bind(window,20)(10));//200 apply中的ctx爲window
      console.log(proxy.toString(10));不會被攔截
    複製代碼
  • construct(target, args): 攔截 Proxy 實例做爲構造函數調用的操做,好比new proxy(...args)。construct方法返回的必須是一個對象,不然會報錯。同時攔截對象target也必須是一個能夠被new的對象

    function Foo(x,y){
          this.x=x;
          this.y=y;
      }
      let proxy=new Proxy(Foo,{
          construct(target,args){
              return {total:args[0]*args[1]}
          }
      })
      console.log(new proxy(10,20));//200
    複製代碼

參考連接:
一、ES6中的代理模式-----Proxy
二、ECMAScript6 入門
三、ES6 系列之 defineProperty 與 proxy
四、快來圍觀一下JavaScript的Proxy

相關文章
相關標籤/搜索