Proxy
和Reflect
是 ES6 新增 API。javascript
Reflect
是一個內置的對象,它提供攔截 JavaScript 操做的方法。Reflect不是一個函數對象,所以它是不可構造的。Reflect
的全部的方法都是靜態的就和Math
同樣,目前它尚未靜態屬性。java
Reflect
對象的方法與Proxy
對象的方法相同。編程
Reflect
一共有13個靜態方法:數組
它能夠分爲一部分是是原來存在Object
上的方法,將它轉義到了Reflect
上,並做了小改動,讓方法更加合理。app
defineProperty
與Object.defineProperty相似,可是當對象沒法定義時Object.defineProperty
會報錯而Reflect.defineProperty
不會,它會返回false
,成功時返回true
,若是不是對象仍是會報錯。getPrototypeOf(target)
與Object.getPrototypeOf
同樣,返回指定對象的原型。setPrototypeOf(target, prototype)
與Object.setPrototypeOf
同樣,它將指定對象的原型設置爲另一個對象。getOwnPropertyDescriptor()
與Object.getOwnPropertyDescriptor
同樣,若是在對象中存在,則返回給定的屬性的屬性描述符。isExtensible(target)
與Object.isExtensible
相似,判斷一個對象是否可擴展(是否能夠在它上面添加新的屬性),它們的不一樣點是,當參數不是對象時(原始值),Object
的將它強制轉變爲一個對象,Reflect
是直接報錯。preventExtensions(target)
與Object.preventExtensions
相似,阻止新屬性添加到對象,不一樣點和上一條同樣。apply(func, thisArg, args)
與Function.prototype.apply.call(fn, obj, args)
同樣。ownKeys(target)
與Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))
同樣,返回一個包含全部自身屬性(不包含繼承屬性)的數組另外一部分是將原來操做符的功能,變成函數行爲。編程語言
has(target, key)
與in
操做符同樣,讓判斷操做都變成函數行爲。deleteProperty(target, key)
與delete
操做符同樣,讓刪除操做變成函數行爲,返回布爾值表明成功或失敗。construct(target, argumentsList[, newTarget])
與new
操做符同樣,target
構造函數,第二參數是構造函數參數類數組,第三個是new.target的值。get(target, key[, receiver])
與obj[key]
同樣,第三個參數是當要取值的key
部署了getter
時,訪問其函數的this
綁定爲receiver
對象。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 對象用於定義基本操做的自定義行爲(如屬性查找,賦值,枚舉,函數調用等),等同於在語言層面作出修改,因此屬於一種「元編程」(meta programming),即對編程語言進行編程。函數
Proxy 就像在目標對象之間的一個代理,任何對目標的操做都要通過代理。代理就能夠對外界的操做進行過濾和改寫。post
Proxy
是構造函數,它有兩個參數target
和handler
,ui
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
方法攔截函數的調用
、call
和apply
操做。
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
方法主要用來攔截獲取對象原型,會如下這些操做:
Object.prototype.__proto__
Object.prototype.isPrototypeOf()
Object.getPrototypeOf()
Reflect.getPrototypeOf()
instanceof
ownKeys
方法用來攔截對象自身屬性的讀取操做,會攔截如下操做:
Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
Object.keys()
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
複製代碼
雖然 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
複製代碼