【ES6系列】Proxy和Reflect

本篇目錄
  • Proxy數組

    • Proxy基本概念
    • 常見的Proxy攔截操做app

      • get
      • set
      • has
      • deleteProperty
      • ownKeys
    • Proxy.revocable()
    • Proxy的this問題
  • Reflect函數

    • 設計目的
    • 相關方法
  • Proxy和Reflect實現觀察者模式
  • 小結

豬八戒去高老莊找高翠蘭,結果高小姐是孫悟空變的,在這個場景中,對於豬八戒來講,孫悟空能夠算是高小姐的一個代理,在長相上來講,他們是一致的。豬八戒只能訪問到被孫悟空假扮的高小姐,卻見不到真正的高小姐。

Proxy

Proxy基本概念

在上面的場景中,孫悟空就相似於咱們今天要講的ES6中的Proxy,它是一種「代理」,或者能夠稱之爲「攔截」。外界在對一個對象進行訪問的時候,都先必須經過這層攔截,才能進行訪問。而這個攔截的過程當中能夠對外界的訪問進行過濾和改寫。this

咱們先來看一個例子:設計

let obj = new Proxy({}, {
  get: function (target, key, receiver) {
    console.log(`getting ${key}!`);
    return Reflect.get(target, key, receiver);
  },
  set: function (target, key, value, receiver) {
    console.log(`setting ${key}!`);
    return Reflect.set(target, key, value, receiver);
  }
});

在上面這個例子中,咱們經過Proxy對一個空對象進行了攔截,從新定義了對象屬性的讀取(get)和設置(set)行爲。代理

ES6 原生提供 Proxy 構造函數,用來生成 Proxy 實例。code

let proxy = new Proxy(target, handler);

其中,new Proxy()表示生成一個Proxy實例,target參數表示所要攔截的目標對象,handler參數也是一個對象,用來定製攔截行爲。server

注意,要使得Proxy起做用,必須針對Proxy實例(上例是proxy對象)進行操做,而不是針對目標對象(上例是空對象)進行操做。

若是handler沒有設置任何攔截,那就等同於直接通向原對象。對象

let target = {};
let handler = {};
let proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a // "b"

常見的Proxy攔截操做

Proxy支持的攔截操做有:事件

  • get(target, propKey, receiver):攔截對象屬性的讀取,好比proxy.foo和proxy['foo']。
  • 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)。

下面咱們就幾個常見的攔截進行舉例說明。

默認一個對象:

let obj={
  time:'2017-03-11',
  name:'net',
  _r:123
};

get

// 攔截對象屬性的讀取
get(target,key){
  return target[key].replace('2017','2018')
}

set

// 攔截對象設置屬性
set(target,key,value){
  if(key==='name'){
    return target[key]=value;
  }else{
    return target[key];
  }
}

has

// 攔截key in object操做
has(target,key){
  if(key==='name'){
    return target[key]
  }else{
    return false;
  }
}

deleteProperty

// 攔截delete
deleteProperty(target,key){
  if(key.indexOf('_')>-1){
    delete target[key];
    return true;
  }else{
    return target[key]
  }
}

ownKeys

// 攔截Object.keys,Object.getOwnPropertySymbols,Object.getOwnPropertyNames
ownKeys(target){
  return Object.keys(target).filter(item=>item!='time')
}

Proxy.revocable()

Proxy.revocable方法返回一個能夠取消的Proxy實例

let target = {};
let handler = {};

let {proxy, revoke} = Proxy.revocable(target, handler);

proxy.foo = 123;
proxy.foo // 123

revoke();
proxy.foo // TypeError: Revoked

Proxy.revocable方法返回一個對象,該對象的proxy屬性是Proxy實例,revoke屬性是一個函數,能夠取消Proxy實例。上面代碼中,當執行revoke函數以後,再訪問Proxy實例,就會拋出一個錯誤。

Proxy.revocable的一個使用場景是,目標對象不容許直接訪問,必須經過代理訪問,一旦訪問結束,就收回代理權,不容許再次訪問。

Proxy的this問題

雖然 Proxy 能夠代理針對目標對象的訪問,但它不是目標對象的透明代理,即不作任何攔截的狀況下,也沒法保證與目標對象的行爲一致。主要緣由就是在 Proxy 代理的狀況下,目標對象內部的this關鍵字會指向 Proxy 代理。

const target = {
  m: function () {
    console.log(this === proxy);
  }
};
const handler = {};

const proxy = new Proxy(target, handler);

target.m() // false
proxy.m()  // true

因此在一些狀況下,有些對象的屬性只能經過正確的this才能拿到時,因爲this指向的變化,致使Proxy沒法代理目標對象。

const target = new Date();
const handler = {};
const proxy = new Proxy(target, handler);

proxy.getDate();
// TypeError: this is not a Date object.

Reflect

爲何咱們要一塊兒來講Reflect呢?Reflect對象與Proxy對象同樣,也是 ES6 爲了操做對象而提供的新 API。

設計目的

(1) 將Object對象的一些明顯屬於語言內部的方法(好比Object.defineProperty),放到Reflect對象上。現階段,某些方法同時在Object和Reflect對象上部署,將來的新方法將只部署在Reflect對象上。也就是說,從Reflect對象上能夠拿到語言內部的方法。

(2) 修改某些Object方法的返回結果,讓其變得更合理。好比,Object.defineProperty(obj, name, desc)在沒法定義屬性時,會拋出一個錯誤,而Reflect.defineProperty(obj, name, desc)則會返回false。

// 老寫法
try {
  Object.defineProperty(target, property, attributes);
  // success
} catch (e) {
  // failure
}

// 新寫法
if (Reflect.defineProperty(target, property, attributes)) {
  // success
} else {
  // failure
}

(3) 讓Object操做都變成函數行爲。某些Object操做是命令式,好比name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)讓它們變成了函數行爲。

// 老寫法
'assign' in Object // true

// 新寫法
Reflect.has(Object, 'assign') // true

(4)Reflect對象的方法與Proxy對象的方法一一對應,只要是Proxy對象的方法,就能在Reflect對象上找到對應的方法。這就讓Proxy對象能夠方便地調用對應的Reflect方法,完成默認行爲,做爲修改行爲的基礎。也就是說,無論Proxy怎麼修改默認行爲,你總能夠在Reflect上獲取默認行爲。

Proxy(target, {
  set: function(target, name, value, receiver) {
    var success = Reflect.set(target,name, value, receiver);
    if (success) {
      log('property ' + name + ' on ' + target + ' set to ' + value);
    }
    return success;
  }
});

相關方法

Reflect與ES5的Object有點相似,包含了對象語言內部的方法,Reflect也有13種方法,與proxy中的方法一一對應。

Proxy至關於去修改設置對象的屬性行爲,而Reflect則是獲取對象的這些行爲。

相關使用你們參照Object和Proxy的使用便可,再也不一一贅述。

Proxy和Reflect實現觀察者模式

觀察者模式(Observer mode)指的是函數自動觀察數據對象,一旦對象有變化,函數就會自動執行。

在咱們以前的開發過程當中,咱們若是想要實現觀察者模式的話,咱們可能須要進行事件綁定和觸發來實現。

Event.listen('changeName', name => console.log(name))

Event.trigger('changeName', name )

可是在ES6中,咱們能夠經過使用Proxy和Reflect來實現這個目的

//添加觀察者
const queuedObservers = new Set();
const observe = fn => queuedObservers.add(fn);

//proxy 的set 方法
function set(target, key, value, receiver) {
    const result = Reflect.set(target, key, value, receiver);
    queuedObservers.forEach(observer => observer());
    return result;
}
//建立proxy代理
const observable = obj => new Proxy(obj, {set});
//被觀察的 對象
const person = observable({
  name: '張三',
  age: 20
});

function print() {
    console.log(`${person.name}, ${person.age}`)
}
function print2() {
    console.log(`我是二號觀察者:${person.name}, ${person.age}`)
}
//添加觀察者
observe(print);
observe(print2);
person.name = '李四';
// 輸出
// 李四, 20
// 我是二號觀察者:李四, 20

小結

Proxy和Reflect都是ES6中針對對象新增的方法,Proxy修改設置對象的屬性行爲,而Reflect則是獲取對象的這些行爲。

相關文章
相關標籤/搜索