JS 中的 Reflect 和 Proxy

ProxyReflect是 ES6 新增 API。javascript

Reflect

Reflect是一個內置的對象,它提供攔截 JavaScript 操做的方法。Reflect不是一個函數對象,所以它是不可構造的。Reflect的全部的方法都是靜態的就和Math同樣,目前它尚未靜態屬性。java

Reflect對象的方法與Proxy對象的方法相同。編程

Reflect 一共有13個靜態方法:數組

它能夠分爲一部分是是原來存在Object上的方法,將它轉義到了Reflect上,並做了小改動,讓方法更加合理。app

  1. definePropertyObject.defineProperty相似,可是當對象沒法定義時Object.defineProperty會報錯而Reflect.defineProperty不會,它會返回false,成功時返回true,若是不是對象仍是會報錯。
  2. getPrototypeOf(target)Object.getPrototypeOf同樣,返回指定對象的原型。
  3. setPrototypeOf(target, prototype)Object.setPrototypeOf同樣,它將指定對象的原型設置爲另一個對象。
  4. getOwnPropertyDescriptor()Object.getOwnPropertyDescriptor同樣,若是在對象中存在,則返回給定的屬性的屬性描述符
  5. isExtensible(target)Object.isExtensible相似,判斷一個對象是否可擴展(是否能夠在它上面添加新的屬性),它們的不一樣點是,當參數不是對象時(原始值),Object的將它強制轉變爲一個對象,Reflect是直接報錯。
  6. preventExtensions(target)Object.preventExtensions相似,阻止新屬性添加到對象,不一樣點和上一條同樣。
  7. apply(func, thisArg, args)Function.prototype.apply.call(fn, obj, args)同樣。
  8. ownKeys(target)Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))同樣,返回一個包含全部自身屬性(不包含繼承屬性)的數組

另外一部分是將原來操做符的功能,變成函數行爲。編程語言

  1. has(target, key)in操做符同樣,讓判斷操做都變成函數行爲。
  2. deleteProperty(target, key)delete操做符同樣,讓刪除操做變成函數行爲,返回布爾值表明成功或失敗。
  3. construct(target, argumentsList[, newTarget])new操做符同樣,target構造函數,第二參數是構造函數參數類數組,第三個是new.target的值。
  4. get(target, key[, receiver])obj[key]同樣,第三個參數是當要取值的key部署了getter時,訪問其函數的this綁定爲receiver對象。
  5. set(target, key, value[, receiver]) 設置target對象的key屬性等於value,第三個參數和set同樣。返回一個布爾值。
// 老寫法
'assign' in Object // true

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

// 老寫法
Function.prototype.apply.call(Math.floor, undefined, [1.75]) // 1

// 新寫法
Reflect.apply(Math.floor, undefined, [1.75]) // 1

// 舊寫法
delete myObj.foo;

// 新寫法
Reflect.deleteProperty(myObj, 'foo');

// new 的寫法
const instance = new Greeting('張三');

// Reflect.construct 的寫法
const instance = Reflect.construct(Greeting, ['張三']);

// 舊寫法
Object.defineProperty(MyDate, 'now', {
  value: () => Date.now()
});

// 新寫法
Reflect.defineProperty(MyDate, 'now', {
  value: () => Date.now()
});

Reflect.get(1, 'foo') // 報錯
Reflect.get(false, 'foo') // 報錯
Reflect.set(1, 'foo', {}) // 報錯
Reflect.set(false, 'foo', {}) // 報錯

// ---------------

var myObject = {
  foo: 1,
  bar: 2,
  get baz() {
    return this.foo + this.bar;
  },
};

var myReceiverObject = {
  foo: 4,
  bar: 4,
};

Reflect.get(myObject, 'baz', myReceiverObject) // 8
複製代碼

Proxy

Proxy 對象用於定義基本操做的自定義行爲(如屬性查找,賦值,枚舉,函數調用等),等同於在語言層面作出修改,因此屬於一種「元編程」(meta programming),即對編程語言進行編程。函數

Proxy 就像在目標對象之間的一個代理,任何對目標的操做都要通過代理。代理就能夠對外界的操做進行過濾和改寫。post

Proxy是構造函數,它有兩個參數targethandlerui

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

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

var 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);
  }
});

obj.count = 1
// setting count!
++obj.count
// getting count!
// setting count!
// 2
複製代碼

Proxy只有一個靜態方法revocable(target, handler)能夠用來建立一個可撤銷的代理對象。兩個參數和構造函數的相同。它返回一個包含了所生成的代理對象自己以及該代理對象的撤銷方法的對象。

一旦某個代理對象被撤銷,它將變的幾乎徹底不可用,在它身上執行任何的可代理操做都會拋出 TypeError 異常(注意,可代理操做一共有 14 種,執行這 14 種操做之外的操做不會拋出異常)。一旦被撤銷,這個代理對象永遠不可能恢復到原來的狀態,同時和它關聯的目標對象以及處理器對象將有可能被垃圾回收掉。調用撤銷方法屢次將不會有任何效果,固然,也不會報錯。

var revocable = Proxy.revocable({}, {
  get(target, name) {
    return "[[" + name + "]]";
  }
});

// revocable -> {"proxy": proxy, "revoke": revoke}

var proxy = revocable.proxy;
proxy.foo;              // "[[foo]]"

revocable.revoke();     // 執行撤銷方法

proxy.foo;              // TypeError
proxy.foo = 1           // 一樣 TypeError
delete proxy.foo;       // 仍是 TypeError
typeof proxy            // "object",由於 typeof 不屬於可代理操做
複製代碼

handler參數是代理函數對象,它一共支持 13 種攔截函數。和Reflect的相同。若是沒有定義某種操做,那麼這種操做會被轉發到目標對象身上。

const proxy = new Proxy({}, {
  get: function(target, property, receiver) {
    return receiver;
    // receiver 老是指向原始的讀操做所在的那個對象,通常狀況下就是 Proxy 實例。
  }
});
proxy.getReceiver === proxy // true
複製代碼

若是一個屬性不可配置(configurable)且不可寫(writable),則 Proxy 不能修改該屬性,不然經過 Proxy 對象訪問該屬性會報錯。

const target = Object.defineProperties({}, {
  foo: {
    value: 123,
    writable: false,
    configurable: false
  },
});

const handler = {
  get(target, propKey) {
    return 'abc';
  }
};

const proxy = new Proxy(target, handler);

proxy.foo
// TypeError: Invariant check failed
複製代碼

apply方法攔截函數的調用callapply操做。

var target = function () { return 'I am the target'; };
var handler = {
  apply: function () {
    return 'I am the proxy';
  }
};

var p = new Proxy(target, handler);

p()
// "I am the proxy"
複製代碼

defineProperty方法攔截了Object.defineProperty操做。

var handler = {
  defineProperty (target, key, descriptor) {
    return false;
  }
};
var target = {};
var proxy = new Proxy(target, handler);
proxy.foo = 'bar' // 不會生效
// defineProperty 方法返回 false,致使添加新屬性老是無效。
複製代碼

注意,若是目標對象不可擴展(non-extensible),則defineProperty不能增長目標對象上不存在的屬性,不然會報錯。另外,若是目標對象的某個屬性不可寫(writable)或不可配置(configurable),則defineProperty方法不得改變這兩個設置。

getPrototypeOf方法主要用來攔截獲取對象原型,會如下這些操做:

  1. Object.prototype.__proto__
  2. Object.prototype.isPrototypeOf()
  3. Object.getPrototypeOf()
  4. Reflect.getPrototypeOf()
  5. instanceof

ownKeys方法用來攔截對象自身屬性的讀取操做,會攔截如下操做:

  1. Object.getOwnPropertyNames()
  2. Object.getOwnPropertySymbols()
  3. Object.keys()
  4. for...in

經過代理,你能夠輕鬆地驗證向一個對象的傳值。

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

    // The default behavior to store the value
    obj[prop] = value;
  }
};

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

person.age = 100;

console.log(person.age); 
// 100

person.age = 'young'; 
// 拋出異常: Uncaught TypeError: The age is not an integer

person.age = 300; 
// 拋出異常: Uncaught RangeError: The age seems invalid
複製代碼

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
複製代碼
const target = new Date();
const handler = {};
const proxy = new Proxy(target, handler);

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

// getDate 方法只能在Date對象實例上面拿到,
// 若是this不是Date對象實例就會報錯。
// 這時,this綁定原始對象,就能夠解決這個問題

const target = new Date('2015-01-01');
const handler = {
  get(target, prop) {
    if (prop === 'getDate') {
      return target.getDate.bind(target);
    }
    return Reflect.get(target, prop);
  }
};
const proxy = new Proxy(target, handler);

proxy.getDate() // 1
複製代碼
相關文章
相關標籤/搜索