ES6-Proxy Reflect 入門學習

realmofwind發表於2019-08-02

ProxyReflect是ES6中新增的用來操作物件的API。

Proxy

  • Proxy原意是代理的意思,這裡表示由它來“代理”某些操作,也稱為“代理器”。
  • Proxy可以理解成在目標物件前假設一層“攔截層” ,外界對該物件的訪問都必須先通過這層攔截,因此也提供了一種機制,可以對外界的訪問就行過濾和改寫。也可以理解為“資料劫持”。
    ES6-Proxy Reflect 入門學習

Proxy基礎語法

ES6-Proxy Reflect 入門學習

proxy一共可以可以代理(劫持或叫trap)13種物件的操作,如下:

  • 常用:set、get、has、deleteProperty、apply
  • 其他:getPrototypeOf、setPrototypeOf、isExtensible、preventExtensions、getOwnPropertyDescription、defineProperty、ownKeys、construct

Proxy基礎應用

let test = {
  name: 'zyh',
  boy: {
    name: 'zy',
    age:0
  }
}
var obj = new Proxy(test,{
  get: function(target, key, receiver){
    console.log('getting')
    return Reflect.get(target, key, receiver)
  },
  set: function(target, key, value, receiver){
    console.log('setting')
    return Reflect.set(target, key, value, receiver)
  }
})
obj.count = 1
// setting
複製程式碼

上面的例子可以看作Proxy過載(overload)了點運算子。
函式也是一個物件,so,Proxy也可以代理一個函式。

var func = (a, b)=> {
 return a + b
}
var fproxy = new Proxy(func, {
  apply: function (target, ctx, args) {
    console.log('do sth')
    return target(...args)
  }
})
複製程式碼
  • Proxy 能夠讓我們以簡潔易懂的方式控制外部對物件的訪問。功能類似於設計模式中的代理模式。
  • 某些時候我們可以把一些非核心邏輯交給Proxy處理,例如訪問日誌記錄、訪問控制、屬性驗證等,達到關注點分離的目的。

表單驗證demo

  // 校驗工具函式
  var isNotEmpty = value => value !== '';

  var isNumber = value => /^[0-9]*$/.test(value);

  var isBetween = (value, min, max) => {
    if (max === undefined) {
      max = Number.MAX_VALUE;
    }
    if (min === undefined) {
      min = Number.MIN_VALUE;
    }
    return value > min && value < max;
  }

  // 校驗器
  let validators = {
    name: [{
      validator: isNotEmpty,
      errorMsg: '姓名不能為空'
    }],
    age: [
      {
        validator: isNumber,
        errorMsg: '年齡必須為數字'
      },
      {
        validator: isBetween,
        errorMsg: '年齡必須為大於 0 並且小於 100',
        params: [0, 100]
      }
    ]
  }

  var validatorCreater = (target, validator) => new Proxy(target, {
  // 儲存校驗器
  _validator: validator,
  set(target, key, value, receiver) {
    // 如果賦值的屬性存在校驗器,則進行校驗
    if (this._validator[key]) {
      // 遍歷其多個子校驗器
      for (validatorStrategy of this._validator[key]) {
        let { validator, errorMsg = '', params = [] } = validatorStrategy;
        if (!validator.call(null, value, ...params)) {
          throw new Error(errorMsg);
          return false;
        }
      }
    }
    // 賦值語句放最後,如果失敗不賦值,如果不存在校驗器則賦值
    return Reflect.set(target, key, value, receiver);
  }
  })
複製程式碼

完整demo

Proxy進階

Proxy如果劫持了陣列的set方法,那麼是可以檢測到陣列的變化的,這一點比它的前輩Object.defineProperty算是進步的,但Proxy本身也是不支援複雜物件(物件的值也是物件)的代理,so如果你要劫持一下複雜物件的方法,那需要自己用遞迴處理一下。

const obj = {
    info: {
      name: 'eason',
      blogs: ['webpack', 'babel', 'postcss']
    }
  }
  let handler = {
    get(target, key, receiver) {
      console.log('get', key)
      // 遞迴建立並返回
      if (typeof target[key] === 'object' && target[key] !== null) {
        return new Proxy(target[key], handler)
      }
      return Reflect.get(target, key, receiver)
    },
    set(target, key, value, receiver) {
      console.log('set', key, value)
      return Reflect.set(target, key, value, receiver)
    }
  }
  let proxy = new Proxy(obj, handler)
複製程式碼

上面例子中的obj就是個複雜物件,舉個例子,當我們執行obj.info.name = 2時,控制檯輸出如下:

ES6-Proxy Reflect 入門學習

我們可以把上面這行程式碼拆成兩步走:1)obj.info,這是因為我們劫持了obj的get方法,此時發現我們要取的這個值是個物件,這裡就會遞迴呼叫new Proxy()來把這個物件也代理一下。2)obj.info.name=2這時因為我們設定了info這個代理物件的name值,所有自然會被列印出set的日誌。這個例子還是很直觀的告訴我們proxy是如何代理複雜物件的。
完整例子

Reflect

其實上面的例子中,我們已經用到了Reflect,Reflect物件某種意義上也可以說是為Proxy準備的,Proxy的handler中的各個trap都有對應的Reflect的同名方法。Reflect與Proxy搭配使用,可以讓Proxy物件方便地呼叫對應的Reflect方法完成預設行為,作為修改行為的基礎。
下面簡單介紹一下:

Reflect基礎

  • Reflect是一個內建物件,和Proxy一樣也是ES6為了操作物件提供的新API。
  • Reflect不是一個函式物件,沒有建構函式。不能與new運算子一起使用。
  • Reflect的所有屬性和方法都是靜態的(像Math物件一樣)

小結

  • Proxy讓開發者很方便地使用代理模式,業務邏輯清晰。
  • Proxy 不但可以取代 Object.defineProperty 並且還擴增了非常多的功能。Proxy 技術支援監測陣列的 push 等方法,支援物件屬性的動態新增和刪除,極大的簡化了響應化的程式碼量。