Proxy詳解


一.Proxy基礎

1. 什么是Proxy?

Proxy是一個構造函數,可以通過它生成一個Proxy實例。

const proxy = new Proxy(target, handler);
// target: 目標對象,待操作對象(可以是普通對象,數組,函數,proxy實例對象)
// handler: 一個對象,最多可包含13個操作方法,也可以是空對象

2. Proxy的作用

1. Proxy是一個ES6語法,它的作用主要是通過handler對象中的攔截方法攔截目標對象target的

某些操作(如讀取,賦值,函數調用,new等),然后過濾或者改寫這些操作的默認行為,

或者只是在完成這些操作的默認行為的基礎上,增加一些副作用(如打印前置log等)。

2. 生成的實例對象是針對target對象的“攔截器”。也可以叫做“代理器”。

3. 然后必須通過操作proxy,即“攔截器”(攔截器對象本身性質和目標對象target一樣,比如:target是函數,

那么proxy也是函數)才能觸發攔截方法,來完成對目標對象的操作和訪問。

4. 如果handler是個空對象({}),那么操作攔截器相當於直接操作目標對象target。

3. Proxy構造函數的特征

1. Proxy函數沒有prototype屬性,所以也就不能使用instanceof判斷是否是Proxy實例

2. Proxy實例的數據類型和target數據類型一致。

var proxy1 = new Proxy([], {});
proxy1 instanceof Array; // true
var proxy2 = new Proxy(function(){}, {});
typeof proxy2; //"function"

二. Proxy攔截器handler方法

一共有13個攔截方法(對應Reflect的13個方法),可以大體分為兩個部分。

1. 新的方法名

返回值是布爾值的方法有:

1. has(target, propKey)

作用:  攔截判斷target對象是否含有屬性propKey的操作

攔截操作: propKey in proxy;   不包含for...in循環

對應Reflect: Reflect.has(target, propKey)

語法:

const handler = {
    has(target, propKey){
        // ...
        //默認操作
        return propKey in target;
    }
}
const proxy = new Proxy(target, handler)

示例: 過濾某些私有屬性

  const handler = {
    has(target, propKey) {
      if (propKey[0] === '_') {
        return false;
      }
      return propKey in target;
    }
  }
  const target = {_myprop: 1, a: 2, c:3};
  const proxy = new Proxy(target, handler);
  '_myprop' in proxy; // false

特殊情況: 目標target是不可擴展或者某個屬性不可配置,只能返回默認行為結果;否則報錯

var obj = { a: 10 };
Object.preventExtensions(obj);

var p = new Proxy(obj, {
  has: function(target, prop) {
    return false; //只能是return prop in target;
  }
});
'a' in p; //Uncaught TypeError: 'has' on proxy: trap returned falsish for property 'a' but the proxy target is not extensible

2. deleteProperty(target, propKey)

返回:嚴格模式下只有返回true, 否則報錯

作用: 攔截刪除target對象的propKey屬性的操作

攔截操作: delete proxy[propKey]

語法: 

const handler = {
    deleteProperty(target, propKey){
        // ...
        //默認操作; 操作成功並返回true;操作失敗報錯
        const bool = Reflect.deleteProperty(...arguments);
        if (bool) {
            return bool;
        } else {
            throw new Error("delete failed");
        }
    }
}
const proxy = new Proxy(target, handler)

示例:

var obj = { a: 10 };
var p = new Proxy(obj, {
  deleteProperty(target, prop) {
    console.log('delete propName ',prop); 
    return delete target[prop]; // 嚴格模式下操作成功必須返回true;否則報錯
  }
});
delete p.a;
console.log(obj);
// 運行結果如下:
'delete propName a'
{}

 

特殊情況: 屬性是不可配置屬性時,不能刪除; 但是對象不可擴展的時候,可以刪除屬性。

var obj = { a: 10 };
Object.defineProperty(obj, 'b', {
  value: 2, configurable: false
});
var p = new Proxy(obj, {
  deleteProperty(target, prop) {
    return delete target[prop];
  }
});
delete p.b; // Uncaught TypeError: Cannot delete property 'b' of #<Object>
console.log(obj);

3. ownKeys(target)

返回: 數組(數組元素必須是字符或者Symbol,其他類型報錯)

作用: 攔截獲取鍵值的操作

攔截操作: Object.getOwnPropertyNames(proxy)

                Object.getOwnPropertySymbols(proxy)

                Object.keys(proxy)

                for...in...循環

語法: 

最后取到的結果不是return的值,而是會自動過濾

const handler = {
   ownKeys(target) {
// 所有的keys;也可以是其他的數組
return Reflect.ownKeys(target); } }

示例: 

var obj = { a: 10, [Symbol.for('foo')]: 2 };
Object.defineProperty(obj, 'c', {
  value: 3, enumerable: false
})
var p = new Proxy(obj, {
  ownKeys(target) {
    return [...Reflect.ownKeys(target), 'b', Symbol.for('bar')];
  }
});
const keys = Object.keys(p);  // ['a']
// 自動過濾掉Symbol/非自身/不可遍歷的屬性

for(let prop in p) { // 和Object.keys()過濾性質一樣,只返回target本身的可遍歷屬性
  console.log('prop-',prop); // prop-a
}
const ownNames = Object.getOwnPropertyNames(p);// ['a', 'c', 'b']
// 只返回攔截器返回的非Symbol的屬性,不管是不是target上的屬性

const ownSymbols = Object.getOwnPropertySymbols(p);// [Symbol(foo), Symbol(bar)]
// 只返回攔截器返回的Symbol的屬性,不管是不是target上的屬性

const ownKeys = Reflect.ownKeys(p);// ['a','c',Symbol(foo),'b',Symbol(bar)]
// 返回攔截器返回的所有值

特殊情況:

1)如果某個屬性是不可配置的,那么該屬性在攔截器中必須被返回,否則報錯

2)如果target對象是不可擴展的,那么攔截器返回的數組必須是操作的默認返回結果。

即必須是被攔截的操作的默認行為,如getOwnPropertyNames()

4. apply(target, thisArg, args)--target是函數

返回:函數執行結果

作用: 攔截proxy作為函數調用時的操作

攔截操作:proxy()

               proxy.call(obj, ...args)

               proxy.apply(obj, [...args])

               Reflect.apply(proxy, thisArg, args)

語法:

const handler = {
  apply(target, contextThis/*上下文對象this*/, args/*參數數組*/) {
    return Reflect.apply(...arguments);
  }
}

示例:

const handler = {
  apply(target, contextThis/*上下文對象this*/, args/*參數數組*/) {
    console.log('---apply攔截器---',contextThis,'-',args);
    return Reflect.apply(...arguments)+'end';
  }
}
let target = function(a,b) {
  return a+b;
}
const proxy = new Proxy(target, handler);
console.log('proxy-->',proxy(5,6));
console.log('proxy.call-->',proxy.call(null, 5, 6));
console.log('proxy.apply-->',proxy.apply(null, [5,6]));
console.log('Reflect-->',Reflect.apply(proxy, null, [5,6]));

// 運行結果如下:
/*
---apply攔截器---undefined-[5,6]
proxy-->11end
---apply攔截器---null-[5,6]
proxy.call-->11end
---apply攔截器---null-[5,6]
proxy.apply-->11end
---apply攔截器---null-[5,6]
Reflect-->11end
*/

5. construct(target, args, newTarget)--target是構造函數

返回: 實例對象, 不是對象會報錯 

作用: 攔截new命令

攔截操作: new proxy(...args)

語法:

const handler = {
  construct(target, args, newTarget){// args是參數數組, newTarget是生成的proxy實例
    console.log('----攔截new----',args,'-', newTarget === proxy);
    return Reflect.construct(target, args);// 默認行為,也可以return {a: b},只要是對象就可以
  }
}
const target = function(a,b) {};
var proxy = new Proxy(target, handler);
console.log('proxy type-->', typeof proxy);
const result = new proxy(1,2); // 觸發攔截器
console.log('result type-->',typeof result)
// 運行結果如下: proxy type-->function ----攔截new----[1,2]-true result type-->object

2. 方法名和對象原有方法名一樣

6. get(target, propKey, receiver)

返回: 返回讀取的屬性

作用:攔截對象屬性的讀取

攔截操作:proxy[propKey]或者點運算符

語法:

const handler = {
  get(target, propKey, receiver) {// receiver是proxy實例
    return Reflect.get(target, propKey);
  }
}

示例: 實現函數的鏈式操作

const funsObj = {
  double(n) {return n*2},
  square(n) {return n**2}
}
var pipe = (value) => {
  const callStack=[];
  return new Proxy({}, {
    get(target, propKey, receiver) {
      console.log(propKey,callStack);
      if (propKey === 'get') {
        return callStack.reduce((val, fn) => {
          return fn(val);
        }, value)
      } else {
        callStack.push(funsObj[propKey]);
      }
      return receiver; //返回proxy實例才能實現鏈式調用
    }
  })
}
pipe(3).double.square.get; //36

get方法可以繼承,但是receiver的值會是直接觸發的那個對象。

const proxy= new Proxy({}, {
  get(target, propKey, receiver) {
    return receiver;
  }
})
const p = Object.create(proxy);
console.log(p.a === p); // true p.a返回receiver

特殊情況:如果對象的屬性writable和configurable同時為false, 則攔截器只能返回默認行為

const target = {};
Object.defineProperty(target, 'a', {
  value: 1, 
  writable: false,
  configurable: false
})
const proxy = new Proxy(target, {
  get(target,propKey) {
    return 2;
    //應該return 1;不能返回其他值,否則報錯
  }
})
proxy.a; // Uncaught TypeError

7.set(target,propKey, value,receiver)

返回:嚴格模式下返回true操作成功;否則失敗,報錯

作用: 攔截對象的屬性賦值操作

攔截操作: proxy[propkey] = value

語法:

 

const proxy = new Proxy({}, {
  set(target, propKey, value,receiver) {// receiver時proxy實例
    const bool = Reflect.set(...arguments); 
    if (bool){
      return //!!!嚴格模式下操作成功必須返回true,否則報錯;非嚴格模式下可以省略
//當Reflect.set傳入的參數有receiver且屬性writable=true時,會在receiver在定義屬性,會觸發defineProperty攔截
    } else {
        throw new Error("操作失敗")
    }
    
  },
  defineProperty(target, propKey, propDesc) {
    console.log('defineProperty')
  }
})

set方法也可以繼承,receiver也是最終調用的那個實例,和get方法一樣。參照get的方法。

設置 target[propKey] = receiver;

當對象的屬性writable為false時,該屬性不能在攔截器中被修改;

const obj = {};
Object.defineProperty(obj, 'foo', {
  value: 'bar',
  writable: false,
  configurable: true,
});

const handler = {
  set: function(obj, prop, value, receiver) {
    return Reflect.set(...arguments);
  },
};
const proxy = new Proxy(obj, handler);
proxy.foo = 'baz';
console.log(obj); // {foo: bar} 說明修改失敗

8. defineProperty(target, propKey,PropDesc)

返回: 嚴格模式下操作成功必須返回true;否則報錯

作用:攔截定義屬性或者重新定義屬性操作

攔截操作: Object.defineProperty(proxy, propKey,propDesc)

                Reflect.defineProperty(proxy, propKey,propDesc)

語法兼示例:

const proxy = new Proxy({}, {
  defineProperty(target, propKey, propDescriptor) {// 最后一個參數是屬性描述對象
    const bool = Reflect.defineProperty(...arguments);
    if (bool) {
       return bool;// !!嚴格模式下操作成功必須返回true,否則報錯
    } else {
       throw new Error("定義屬性失敗")
    }
  }
})
Object.defineProperty(proxy, 'a', {value:5})

特殊情況:

如果對象是不可擴展的(preventExtensible(obj)),則不能添加屬性;

如果對象的某屬性writable和configurable同時為false, 則不能重新定義該屬性的值;

如果上面的屬性有其中一個是true,可以重新定義該屬性的值。

9. getPrototypeOf(target)

返回: 返回對象或者null,否則報錯

作用:攔截獲取原型對象的操作

攔截操作:Object.getPrototypeOf(proxy)

               proxy.__proto__

              Object.isPrototypeOf(proxy)

              Reflect.getPrototypeOf(proxy)

              instanceof

語法:

var p = new Proxy({}, {
  getPrototypeOf(target) {
    return Reflect.getPrototypeOf(target);
  }
});

示例:

var proxy = new Proxy({}, {
  getPrototypeOf(target) {
    return {a:1}
  }
});
Object.getPrototypeOf(proxy); // {a:1} 

特殊情況:

如果目標對象是不可擴展的,那么只能返回默認的原型對象。

10.setPrototypeOf(target, proto)

返回:嚴格模式下返回true,否則報錯;只能是布爾值

作用:攔截設置原型對象的操作

攔截操作:Object.setPrototypeOf(proxy, proto)

               proxy.__proto__

               Reflect.setPropertyOf(proxy,proto)

語法:

var proxy = new Proxy({}, {
  setPrototypeOf(target, proto) {
    const bool = Reflect.setPrototypeOf(...arguments);
    if (bool) {
       return bool; // !!!嚴格模式下操作成功必須返回true;否則報錯
    } else {
       throw new Error("設置原型對象失敗");
    }
  }
})

特殊情況:

如果對象不可擴展,只能進行默認行為。不能修改。

11. isExtensible(target)---只能返回默認行為結果

返回:布爾值

 作用: 攔截是否可擴展方法的調用

攔截操作: Object.isExtensible(proxy)

                Reflect.isExtensible(proxy)

語法:

const proxy = new Proxy({}, {
  isExtensible(target) {
    return Reflect.isExtensible(target); //!!!返回結果只能是這個;即proxy和target的可擴展性必須是一致的
  }
})

12. preventExtensible(target)--遵循默認行為

返回:嚴格模式下返回true,否則報錯;

攔截操作:Object.preventExtensible(proxy)

               Reflect.preventExtentsible(proxy)

語法:

const proxy = new Proxy({}, {
  preventExtensions(target) {
    return Reflect.preventExtensions(target); //嚴格模式下,必須存在
  }
})

13. getOwnPropertyDescriptor(target, propKey)

返回: 對象或者undefined

攔截操作: Object.getOwnPropertyDescriptor(proxy)

                Reflect.getOwnPropertyDescriptor(proxy)

語法:

const proxy = new Proxy({}, {
  getOwnPropertyDescriptor(target, propKey) {
    return Reflect.getOwnPropertyDescriptor(target,propKey);
  }
})

三. 靜態方法-Proxy.revocable()

作用: 返回一個可取消的proxy實例

語法:

const {proxy, revoke} = Proxy.revocable(target,handler);
// proxy是實例
// revoke是個方法;直接調用后取消proxy實例

示例:

const {proxy, revoke} = Proxy.revocable({},{});
proxy.a=5;
revoke();
proxy.a // Uncaught TypeError: Cannot perform 'get' on a proxy that has been revoked

應用: 

用於一個只能訪問一次的代理器

四. this指向

攔截器方法中的this指向proxy實例

五.Proxy應用

觀察者模式:通過攔截對象的賦值操作,實時調用觀察函數。

如: mobx中的observable, observe的實現;通過函數觀察對象,並實時作出反應

const obj = {name: 'lyra', age: 18};//被觀察者--使用Proxy
const fn1 = () => {
    obj.age = 16;
    console.log(`Hello, ${obj.name}, ${obj.age}`);
}
const fn2 = () => console.log('there must be sth changed'); 
const observeFuncs = new Set();
const observe = (fn) => observeFuncs.add(fn);
const observable = function(obj) {
    return new Proxy(obj, {
        set(target, prop, value) {
            observeFuncs.forEach(observer => observer()); //被觀察者屬性被賦值時,觸發觀察者函數調用
            return Reflect.set(...arguments); //默認行為
        }
    })
}
const objProxy = observable(obj);//指定被觀察者對象
observe(fn1); //指定觀察者函數
observe(fn2); 
objProxy.name = 'lyraLee';
// 運行結果如下:
Hello, lyraLee, 16
there must be sth changed

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM