let target = { name: 'target' } let proxy = new Proxy(target, { /** * * * @param {any} trapTarget 用於接收屬性(代理的目標)的對象 * @param {any} key 要寫入的屬性鍵 * @param {any} value 被寫入的屬性的值 * @param {any} receiver 操做發生的對象(一般是代理) */ set(trapTarget, key, value, receiver) { if (!trapTarget.hasOwnProperty(key)) { if (isNaN(value)) { throw new TypeError('屬性必須是數字') } } return Reflect.set(trapTarget, key, value, receiver) } }) proxy.count = 1; console.log(proxy.count)//1 console.log(target.count)//1 proxy.name = 'proxy' console.log(proxy.name)//proxy console.log(target.name)//proxy proxy.anthorName = 'test'// 拋錯
let proxy = new Proxy({}, { get(trapTarget, key, receiver) { if (!(key in receiver)) { throw new TypeError('屬性' + key + '不存在') } return Reflect.get(trapTarget,key,receiver) } }) proxy.name='proxy' console.log(proxy.name)//proxy console.log(proxy.nme)//拋出錯誤
能夠用in操做符來檢測給定對象中是否包含有某個屬性,若是自有屬性或原型屬性匹配這個名稱或Symbol就返回true數組
let target = { name: 'target', value: 42 } let proxy = new Proxy(target, { has(trapTarget, key) { if (key === 'value') { return false } return Reflect.has(trapTarget, key) } }) console.log('value' in proxy)//false console.log('name' in proxy)//true console.log('toString' in proxy)//true
不可配置屬性name用delete操做返回的是false,若是在嚴格模式下還會拋出錯誤
能夠經過deleteProperty陷阱來改變這個行爲app
let target = { name: 'target', value: 42 } let proxy = new Proxy(target, { deleteProperty(trapTarget, key) { if (key === 'value') { return false } return Reflect.deleteProperty(trapTarget, key) } }) console.log('value' in proxy)//true let result1 = delete proxy.value console.log(result1)//false console.log('value' in proxy)//true //嘗試刪除不可配置屬性name 若是沒有使用代理則會返回false而且刪除不成功 console.log('name' in proxy)//true let result2 = delete proxy.name; console.log(result2)//true console.log('name' in proxy)//false
let target = {} let proxy = new Proxy(target, { getPrototypeOf(trapTarget) { //必須返回對象或null,只要返回的是值類型必將致使運行時錯誤 return null; }, setPrototypeOf(trapTarget, proto) { // 若是操做失敗則返回false 若是setPrototypeOf返回了任何不是false的值,那麼Object.setPrototypeOf便設置成功 return false } }) let targetProto = Object.getPrototypeOf(target); let proxyProto = Object.getPrototypeOf(proxy) console.log(targetProto === Object.prototype)//true console.log(proxyProto === Object.prototype)//false Object.setPrototypeOf(target, {})//成功 Object.setPrototypeOf(proxy, {})//拋出錯誤
再看一下下面的代碼函數
let target = {} let proxy = new Proxy(target, { getPrototypeOf(trapTarget) { return Reflect.getPrototypeOf(trapTarget); }, setPrototypeOf(trapTarget, proto) { return Reflect.setPrototypeOf(trapTarget,proto) } }) let targetProto = Object.getPrototypeOf(target); let proxyProto = Object.getPrototypeOf(proxy) console.log(targetProto === Object.prototype)//true console.log(proxyProto === Object.prototype)//true Object.setPrototypeOf(target, {})//成功 Object.setPrototypeOf(proxy, {})//成功
再來講說Object.getPrototypeOf和Reflect.getPrototypeOf的異同點吧
1若是傳入的參數不是對象,則Reflect.getPrototypeOf方法會拋出錯誤,而Object.getPrototypeOf方法則會在操做執行前先將參數強制轉換爲一個對象(對於Object.getPrototypeOf也是一樣)this
let result = Object.getPrototypeOf(1) console.log(result === Number.prototype)//true Reflect.getPrototypeOf(1)//拋錯
let target = {} let proxy = new Proxy(target, { isExtensible(trapTarget) { return Reflect.isExtensible(trapTarget) }, preventExtensions(trapTarget) { return Reflect.preventExtensions(trapTarget) } }) console.log(Object.isExtensible(target))//true console.log(Object.isExtensible(proxy))//true Object.preventExtensions(proxy) console.log(Object.isExtensible(target))//false console.log(Object.isExtensible(proxy))//false
比方說你想讓Object.preventExtensions失敗,可返回false,看下面的例子prototype
let target = {} let proxy = new Proxy(target, { isExtensible(trapTarget) { return Reflect.isExtensible(trapTarget) }, preventExtensions(trapTarget) { return false } }) console.log(Object.isExtensible(target))//true console.log(Object.isExtensible(proxy))//true Object.preventExtensions(proxy) console.log(Object.isExtensible(target))//true //書上說這裏會返回true,但是我本身運行的時候就已經拋出錯誤了 console.log(Object.isExtensible(proxy))//true
Object.isExtensible和Reflect.isExtensible方法很是類似,只有當傳入非對象值時,Object.isExtensible返回false而Reflect.isExtensible則拋出錯誤代理
let result1 = Object.isExtensible(2) console.log(result1)//false let result2 = Reflect.isExtensible(2)
Object.preventExtensions和Reflect.preventExtensions很是相似,不管傳入Object.preventExtensions方法的參數是否爲一個對象,它老是返回該參數,而若是Reflect.preventExtensions方法的參數不是一個對象則會拋出錯誤,若是參數是一個對象,操做成功時Reflect.preventExtensions會返回true不然返回falsecode
let result1 = Object.preventExtensions(2) console.log(result1)//2 let target = {} let result2 = Reflect.preventExtensions(target) console.log(result2)//true let result3 = Reflect.preventExtensions(3)///拋出錯誤
let proxy = new Proxy({}, { defineProperty(trapTarget, key, descriptor) { if (typeof key === 'symbol') { return false } return Reflect.defineProperty(trapTarget, key, descriptor) } }) Object.defineProperty(proxy, 'name', { value: 'proxy' }) console.log(proxy.name)//proxy let nameSymbol = Symbol('name') //拋錯 Object.defineProperty(proxy, nameSymbol, { value: 'proxy' })
若是讓陷阱返回true而且不調用Reflect.defineProperty方法,則可讓Object.defineProperty方法靜默失效,這既消除了錯誤又不會真正定義屬性
不管將什麼參數做爲第三個參數傳遞給Object.defineProperty方法都只有屬性enumerable、configurable、value、writable、get和set將出如今傳遞給defineProperty陷阱的描述符對象中對象
let proxy = new Proxy({}, { defineProperty(trapTarget, key, descriptor) { console.log(descriptor) console.log(descriptor.value) console.log(descriptor.name) return Reflect.defineProperty(trapTarget, key, descriptor) } }) Object.defineProperty(proxy, 'name', { value: 'proxy', name: 'custom' })
getOwnPropertyDescriptor它的返回值必須是null、undefined或是一個對象,若是返回對象,則對象本身的屬性只能是enumerable、configurable、value、writable、get和set,在返回的對象中使用不被容許的屬性則會拋出一個錯誤繼承
let proxy = new Proxy({}, { getOwnPropertyDescriptor(trapTarget, key) { //在返回的對象中使用不被容許的屬性則會拋出一個錯誤 return { name: 'proxy' } } }) let descriptor = Object.getOwnPropertyDescriptor(proxy, 'name')
Object.defineProperty和Reflect.defineProperty只有返回值不一樣
Object.defineProperty返回第一個參數
Reflect.defineProperty的返回值與操做有關,成功則返回true,失敗則返回falseip
let target = {} let result1 = Object.defineProperty(target, 'name', { value: 'target' }) console.log(target === result1)//true let result2 = Reflect.defineProperty(target, 'name', { value: 'refelct' }) console.log(result2)//false
Object.getOwnPropertyDescriptor若是傳入原始值做爲第一個參數,內部會將這個值強制轉換成一個對象,若調用Reflect.getOwnPropertyDescriptor傳入原始值做爲第一個參數,則會拋出錯誤
let proxy = new Proxy({}, { ownKeys(trapTarget) { return Reflect.ownKeys(trapTarget).filter(key => { return typeof key !== 'string' || key[0] !== "_" }) } }) let nameSymbol = Symbol('name') proxy.name = 'proxy' proxy._name = 'private' proxy[nameSymbol] = 'symbol' let names = Object.getOwnPropertyNames(proxy), keys = Object.keys(proxy), symbols = Object.getOwnPropertySymbols(proxy) console.log(names)//["name"] console.log(keys)//["name"] console.log(symbols)//[Symbol(name)]
儘管ownKeys代理陷阱能夠修改一小部分操做返回的鍵,但不影響更經常使用的操做,例如for of循環,這些不能使用代理爲更改,ownKeys陷阱也會影響for in循環,當肯定循環內部使用的鍵時會調用陷阱
let target = function () { return 42; }, proxy = new Proxy(target, { apply: function (trapTarget, thisArg, argumentList) { return Reflect.apply(trapTarget, thisArg, argumentList) }, construct: function (trapTarget, argumentList) { return Reflect.construct(trapTarget, argumentList) } }); //一個目標是函數的代理看起來也像是一個函數 console.log(typeof proxy)//function console.log(proxy())//42 let instance=new proxy(); //用new建立一個instance對象,它同時是代理和目標的實例,由於instanceof經過原型鏈來肯定此信息,而原型鏈查找不受代理影響,這也就是代理和目標好像有相同原型的緣由 console.log(instance instanceof proxy)//true console.log(instance instanceof target)//true
能夠在apply陷阱中檢查參數,在construct陷阱中來確認函數不會被new調用
function sum(...values) { return values.reduce((pre, cur) => pre + cur, 0) } let sumProxy = new Proxy(sum, { apply: function (trapTarget, thisArg, argumentList) { argumentList.forEach(arg => { if (typeof arg !== 'number') { throw new TypeError('全部參數必須是數字。') } }); return Reflect.apply(trapTarget, thisArg, argumentList) }, construct: function (trapTarget, argumentList) { throw new TypeError('該函數不可經過new來調用') } }) console.log(sumProxy(1, 2, 3, 4, 5))//15 console.log(sumProxy(1, 2, '3', 4, 5))//拋出錯誤 let result = new sumProxy()//拋出錯誤
如下例子是確保用new來調用函數並驗證其參數爲數字
function Numbers(...values) { this.values = values } let NumberProxy = new Proxy(Numbers, { apply: function (trapTarget, thisArg, argumentList) { throw new TypeError('該函數必須經過new來調用') }, construct: function (trapTarget, argumentList) { argumentList.forEach(arg => { if (typeof arg !== 'number') { throw new TypeError('全部參數必須是數字') } }) return Reflect.construct(trapTarget, argumentList) } }) let instance = new NumberProxy(12, 3, 4, 8) console.log(instance.values)// [12, 3, 4, 8] NumberProxy(1, 2, 3, 4)//報錯
看一個不用new調用構造函數的例子:
function Numbers(...values) { if (typeof new.target === 'undefined') { throw new TypeError('該函數必須經過new來調用') } this.values = values } let NumberProxy = new Proxy(Numbers, { apply: function (trapTarget, thisArg, argumentList) { return Reflect.construct(trapTarget, argumentList) } }) let instance = NumberProxy(1, 2, 3, 4) console.log(instance.values)//[1,2,3,4]
覆寫抽象基類構造函數
class AbstractNumbers { constructor(...values) { if (new.target === AbstractNumbers) { throw new TypeError('此函數必須被繼承') } this.values = values } } class Numbers extends AbstractNumbers{} let instance = new Numbers(1,2,3,4,5) console.log(instance.values)//[1, 2, 3, 4, 5] new AbstractNumbers(1,2,3,4,5)//報錯 此函數必須被繼承
手動用代理給new.target賦值來繞過構造函數限制
class AbstractNumbers { constructor(...values) { if (new.target === AbstractNumbers) { throw new TypeError('此函數必須被繼承') } this.values = values } } let AbstractNumbersProxy = new Proxy(AbstractNumbers, { construct: function (trapTarget, argumentList) { return Reflect.construct(trapTarget, argumentList, function () { }) } }) let instance = new AbstractNumbersProxy(1, 2, 3, 4) console.log(instance.values)//[1, 2, 3, 4]
可調用的類構造函數
class Person { constructor(name) { this.name = name; } } let PersonProxy = new Proxy(Person, { apply: function (trapTarget, thisArg, argumentList) { return new trapTarget(...argumentList) } }) let me = PersonProxy('angela') console.log(me.name)//angela console.log(me instanceof Person)//true console.log(me instanceof PersonProxy)//true
可撤銷代理
let target = { name: 'target' } let { proxy, revoke } = Proxy.revocable(target, {}) console.log(proxy.name)//traget revoke() console.log(proxy.name)//報錯
function toUint32(value) { return Math.floor(Math.abs(Number(value))) % Math.pow(2, 32) } function isArrayIndex(key) { let numericKey = toUint32(key) return String(numericKey) == key && numericKey < (Math.pow(2, 32) - 1) } function createMyArray(length = 0) { return new Proxy({ length }, { set(trapTarget, key, value) { let currentLength = Reflect.get(trapTarget, 'length') if (isArrayIndex(key)) { let numericKey = Number(key) if (numericKey >= currentLength) { Reflect.set(trapTarget, 'length', numericKey + 1) } } else if (key === 'length') { if (value < currentLength) { for (let index = currentLength - 1; index >= value; index--) { Reflect.deleteProperty(trapTarget, index) } } } Reflect.set(trapTarget, key, value) } }) } let colors = createMyArray(3) colors[0] = 'red' colors[1] = 'green' colors[2] = 'blue' console.log(colors.length)//3 colors[3] = 'black' console.log(colors[3])//black console.log(colors.length)//4 colors.length = 1 console.log(colors)//{0: "red", length: 1}
若是代理是原型,僅當默認操做繼續執行到原型上時才調用代理陷阱,這會限制代理做爲原型的能力
在原型上使用get陷阱
let target={} let thing=Object.create(new Proxy(target,{ /** * * * @param {any} trapTarget 原型對象 * @param {any} key * @param {any} receiver 實例對象 */ get(trapTarget,key,receiver){ throw new ReferenceError(`${key} doesn't exist`) } })) thing.name='thing' console.log(thing.name)//thing let unknown=thing.unknown//拋出錯誤
在原型上使用set陷阱
let target={} let thing=Object.create(new Proxy(target,{ set(trapTarget,key,value,receiver){ return Reflect.set(trapTarget,key,value,receiver) } })) console.log(thing.hasOwnProperty('name')) //觸發set代理陷阱 thing.name='thing' console.log(thing.name) console.log(thing.hasOwnProperty('name')) //不觸發set代理陷阱 thing.name='boo' console.log(thing.name)//boo
在原型上使用has陷阱
let target = {} let thing = Object.create(new Proxy(target, { has(trapTarget, key) { return Reflect.has(trapTarget, key) } })) //觸發has代理陷阱 console.log('name' in thing)//false thing.name = 'thing' //不觸發has代理陷阱 console.log('name' in thing)//true
將代理用做類的原型
function NoSuchProperty(){} NoSuchProperty.prototype=new Proxy({},{ get(trapTarget,key,receiver){ throw new ReferenceError(`${key} doesn't exist`) } }) let thing=new NoSuchProperty() //在get代理陷阱中拋出錯誤 let result=thing.name
function NoSuchProperty() { } NoSuchProperty.prototype = new Proxy({}, { get(trapTarget, key, receiver) { throw new ReferenceError(`${key} doesn't exist`) } }) class Square extends NoSuchProperty { constructor(length, width) { super() this.length = length; this.width = width } } let shape = new Square(2, 6) let area1 = shape.length * shape.width console.log(area1)//12 let area2 = shape.length * shape.wdth//拋出錯誤