ES6-Proxy與數據劫持(12)

隨着前端界的空前繁榮,各類框架橫空出世,包括各種mvvm框架百家爭鳴,好比Anglar、Vue、React等等,它們最大的優勢就是能夠實現數據綁定,不再須要手動進行DOM操做了,它們實現的原理也基本上是髒檢查或數據劫持。咱們先以Vue框架出發,探索其中數據劫持的奧祕。前端

Vue 2.0的版本所使用的數據劫持,說白了就是經過Object.defineProperty()來劫持對象屬性的setter和getter操做,在數據變更時作你想要作的事情,舉個栗子:webpack

var data = {
    name:'xiaoming'
}

Object.keys(data).forEach(function(key){
    Object.defineProperty(data,key,{
        get:function(){
            console.log('get');
        },
        set:function(){
            console.log('監聽到數據發生了變化');
        }
    })
});
data.name //控制檯會打印出 「get」
data.name = 'xiaohong' //控制檯會打印出 "監聽到數據發生了變化"

可是有沒有比Object.defineProperty更好的實現方式呢?web

答案是確定的有,那就是咱們今天的主人公:Proxy編程

一、Proxy簡介數組

Proxy這個詞的原意是代理,用在這裏表示由它來代理某些操做,能夠譯爲代理器。babel

也能夠理解成在目標對象以前設置一層攔截,外界對該對象的訪問,都必須先經過這層攔截,所以提供了一種機制,能夠對外界的訪問進行過濾和改寫。框架

在生活中,代理模式的場景是十分常見的,例如咱們如今若是有購買海外產品(給女友買一個LV的包包,前提是你要先有個女友,^_^)的需求,更多的是去找代購中介機構,而不是直接去國外買。此時,代購起到的做用就是代理的做用。mvvm

圖片描述
Proxy構造函數可以讓咱們輕鬆的使用代理模式:函數

var proxy = new Proxy(target, handler);

Proxy構造函數中有兩個參數:spa

target是用Proxy包裝的被代理對象(能夠是任何類型的對象,包括原生數組,函數,甚至另外一個代理)。

handler是一個對象,其聲明瞭代理target 的一些操做,其屬性是當執行一個操做時定義代理的行爲的函數。

講的通俗點,如何讓代購幫你買LV的包包呢?

首先,你須要告訴代購你看好了哪款包包,這個款式就是Proxy裏的第一個參數target。

其次就是制定購買策略,例如國外比國內便宜20%,就買2個,便宜40%,就買4個,這個策略就是第二個參數handle。

二、Proxy中的處理方法

Proxy有13種數據劫持的操做,那是至關的強大:

圖片描述

2.1 get方法

get方法是在你獲得某對象屬性值時預處理的方法,接受兩個經常使用參數

  • target:獲得的目標值
  • key:目標的key值,至關於對象的屬性

能夠代購來模擬handle中的get方法,以下

var Bao = {
      name: "LV",
    price:9999,
};
var proxyBao = new Proxy(Bao, {
    get: function(target, key) {
        if (target['price']>5000) {
          return '超出客戶心理價位,不買了';
        } else {
          return '符合客戶心理預期,買買買';
        }
    }
});
proxyBao.price
//"超出客戶心理價位,不買了"

解釋一下:客戶想買一個LV的包,心理價位是5000,把購買目標和需求都告訴了代購,代購詢問了下國外的價格,這款LV的包是9999,超出了客戶的心理價位,因而不買了。

2.2 set方法

set方法用來攔截某個屬性的賦值操做,能夠接受四個參數

  • target:目標值。
  • key:目標的Key值。
  • value:要改變的值。
  • receiver:改變前的原始值。

假定Person對象有一個age屬性,該屬性應該是一個不大於 200 的整數,那麼可使用Proxy保證age的屬性值符合要求。

let validator = {
  set: function(target, key, value) {
    if (key === 'age') {
      if (!Number.isInteger(value)) {
        throw new TypeError('The age is not an integer');
      }
      if (value > 200) {
        throw new RangeError('The age seems invalid');
      }
    }

    // 對於知足條件的 age 屬性以及其餘屬性,直接保存
    target[key] = value;
  }
};

let person = new Proxy({}, validator);

person.age = 100;

person.age // 100
person.age = 'young' // 報錯 The age is not an integer
person.age = 300     // 報錯 The age seems invalid

上面代碼中,因爲設置了存值函數set,任何不符合要求的age屬性賦值,都會拋出一個錯誤,這是數據驗證的一種實現方法。

三、Proxy相比Object.defineProperty的優點

3.1 支持數組

let arr = [1,2,3]
let proxy = new Proxy(arr, {
    get (target, key, receiver) {
        console.log('get', key)
        return Reflect.get(target, key, receiver)
    },
    set (target, key, value, receiver) {
        console.log('set', key, value)
        return Reflect.set(target, key, value, receiver)
    }
})
proxy.push(4)
// 可以打印出不少內容
// get push     (尋找 proxy.push 方法)
// get length   (獲取當前的 length)
// set 3 4      (設置 proxy[3] = 4)
// set length 4 (設置 proxy.length = 4)

Proxy 不須要對數組的方法進行重載,省去了衆多 hack,減小代碼量等於減小了維護成本,並且標準的就是最好的。

3.2 針對對象

在數據劫持這個問題上,Proxy 能夠被認爲是 Object.defineProperty() 的升級版。外界對某個對象的訪問,都必須通過這層攔截。所以它是針對 整個對象,而不是 對象的某個屬性,因此也就不須要對 keys 進行遍歷。

let obj = {
  name: 'Eason',
  age: 30
}
let handler = {
  get (target, key, receiver) {
    console.log('get', key)
    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)
proxy.name = 'Zoe' // set name Zoe
proxy.age = 18 // set age 18

圖片描述

3.3 嵌套支持

本質上,Proxy 也是不支持嵌套的,這點和 Object.defineProperty() 是同樣的。所以也須要經過逐層遍從來解決。Proxy 的寫法是在 get 裏面遞歸調用 Proxy 並返回,代碼以下:

let obj = {
  info: {
    name: 'eason',
    blogs: ['webpack', 'babel', 'cache']
  }
}
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)
// 如下兩句都可以進入 set
proxy.info.name = 'Zoe'
proxy.info.blogs.push('proxy')

四、應用實例

4.1 使用Proxy實現表單校驗

let person = {
    name: 'xiaoming',
    age: 30
}
let handler = {
    set (target, key, value, receiver) {
      if (key === 'name' && typeof value !== 'string') {
        throw new Error('用戶姓名必須是字符串類型')
      }
      if (key === 'age' && typeof value !== 'number') {
        throw new Error('用戶年齡必須是數字類型')
      }
      return Reflect.set(target, key, value, receiver)
    }
}
let boy = new Proxy(person, handler)
boy.name = 'xiaohong' // OK
boy.age = '18' // 報錯  用戶年齡必須是數字類型

五、總結

Proxy本質上屬於元編程非破壞性數據劫持,在原對象的基礎上進行了功能的衍生而又不影響原對象,符合鬆耦合高內聚的設計理念。

通俗的說Proxy在數據外層套了個殼,而後經過這層殼訪問內部的數據,就像下面的圖:

圖片描述
Proxy讓JS開發者很方便的使用代理模式,使函數更增強大,業務邏輯更加清楚。

Proxy 不但能夠取代 Object.defineProperty 而且還擴增了很是多的功能。Proxy 技術支持監測數組的 push 等方法操做,支持對象屬性的動態添加和刪除,極大的簡化了響應化的代碼量。Vue 3.0的也會使用Proxy去實現部分核心代碼。

在業務開發時應該注意Proxy使用場景,當對象的功能變得複雜或者咱們須要進行必定的訪問限制時,即可以考慮使用代理。

相關文章
相關標籤/搜索