前端时间学习Vue的时候,碰到Proxy
,当时就一脸蒙逼了。所以返过头来补一下相关的知识。在JavaScript中有Proxy
和Reflect
的两个概念。最近几天一直在学习这两个概念,今天整整这方面的相关知识点。
术语介绍
Proxy
又称为代理。在现实生活中,大家对代理二字并不会太陌生,比如某产品的代理。打个比方来说,我们要买一台手机,我们不会直接到一家手机厂去买,会在手机的代理商中买。
在JavaScript中,Proxy
用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种元编程,即对编程语言进行编程。
Proxy
可以理解成,在目标对象之前架设一层拦截,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy
这个词的原意是代理,用在这里表示由它来代理某些操作,可以译为代理器。
Reflect
称为反射。它也是ES6中为了操作对象而提供的新的API,用来替代直接调用Object
的方法。Reflect
是一个内置的对象,它提供可拦截JavaScript操作的方法。方法与代理处理程序的方法相同。Reflect
不是一个函数对象,因此它是不可构造的。
Reflect
与大多数全局对象不同,Reflect
没有构造函数。你不能将其与一个new
运算符一起使用,或者将Reflect
对象作为一个函数来调用。
简单的看一个小示例:
let target = {}
let handler = {
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)
}
}
let proxy = new Proxy(target, handler)
console.log(proxy.name)
proxy.name = '大漠'
console.log(proxy.name)
proxy.count = 1
proxy.count++
console.log(proxy.count)
运行的结果如下:
Image may be NSFW.
Clik here to view.
上面代码对一个空对象架设了一层拦截,重新定义了属性的读取(get
)和设置(set
)行为。这里暂时先不解释具体的语法,只看运行结果。
在上面的示例中,我们看到了target
、handler
和trap
。简单对介绍一下:
target
代表了被代理的对象。这是你需要控制对其访问的对象。它始终作为Proxy
构造器的第一个参数被传入,同时它也会被传入每个trap
。handler
是一个包含了你想要拦截和处理的操作的对象。它会被作为Proxy
构造器的第二个参数传入。它实现了Proxy API(比如:get
,set
,apply
等等)。- 一个
trap
代表了handler
中一个被处理的函数。因此,如果要拦截get
请求你需要创建一个get
的trap
。以此类推。
基本语法
前面我们了解了相关的术语,我们来看看他们的基本语法。首先来看看Proxy
的基本语法格式:
// @param {Object} target 用来被代理的对象
// @param {Object} handler 用来设置代理的对象
let proxy = new Proxy(target, handler)
ES6原生提供Proxy
构造函数,用来生成Proxy
实例。在使用过程当中,Proxy
都像上面这种形式,不同的只是handler
参数的写法。其中,new Proxy()
表示生成一个Proxy
实例,target
参数表示所要拦截的目标对象,handler
参数也是一个对象,用来定制拦截行为。
let proxy = new Proxy({}, {
get: function(target, property) {
return property in target ? target[property] : `Error: ${target} object not has ${property} property! `
}
})
console.log(proxy.time)
console.log(proxy.name)
console.log(proxy.title)
proxy.time = new Date()
proxy.name = '大漠'
proxy.title = '切图仔'
console.log(proxy.time)
console.log(proxy.name)
console.log(proxy.title)
Image may be NSFW.
Clik here to view.
上面代码中,作来构造函数,Proxy
接受两个参数。第一个参数是所要代理的目标对象(一个空对象{}
),即如果没有Proxy
的介入,操作原来要访问的就是这个对象;第二个参数是一个配置对象,对于每一个被代码的操作,需要提供一个对应的处理函数,该函数将拦截对应的操作。比如,上面的代码中,配置对象有一个get
方法,用来拦截对目标对象属性的访问请求。get
方法的两个参数分别是target
(目标对象)和property
(所要访问的属性)。
注意,要使得
Proxy
起作用,必须针对Proxy
实例(上例是proxy
对象)进行操作,而不是针对目标对象(上例是空对象)进行操作。
Reflect
只是一个内置的对象,它提供可拦截JavaScript操作的方法。方法与代理处理程序的方法相同(稍后会介绍)。Reflect
不是一个函数对象,因此它是不可构造的。
new Reflect() // 错误的写法
Reflect
提供了一些静态方法,静态方法是指只能通过对象自身访问的方法:
Image may be NSFW.
Clik here to view.
处理器对象
处理器对象handler
一共提供了14
种可代理操作,每种操作的代号(属性名/方法名)和触发这种操作的方式如下:
handler.getPrototypeOf()
:在读取代理对象的原型时触发该操作,比如在执行Object.getPrototypeOf(proxy)
时handler.setPrototypeOf()
:在设置代理对象的原型时触发该操作,比如在执行Object.setprototypeOf(proxy, null)
时handler.isExtensible()
:在判断一个代理对象是否是可扩展时触发该操作,比如在执行Object.isExtensible(proxy)
时handler.preventExtensions()
:在让一个代理对象不可扩展时触发该操作,比如在执行Object.preventExtensions(proxy)
时handler.getOwnPropertyDescriptor()
:在获取代理对象某个属性的属性描述时触发该操作,比如在执行Object.getOwnPropertyDescriptor(proxy, 'foo')
时handler.defineProperty()
:在定义代理对象某个属性时的属性描述时触发该操作,比如在执行Object.defineProperty(proxy,'foo',{})
时handler.has()
:在判断代理对象是否拥有某个属性时触发该操作,比如在执行'foo' in proxy
时handler.get()
:在读取代理对象的某个属性时触发该操作,比如在执行proxy.foo
时handler.set()
:在给代理对象的某个赋值时触发该操作,比如在执行proxy.foo = 1
时handler.deleteProperty()
:在删除代理对象的某个属性时触发该操作,比如在执行delete proxy.foo
时handler.ownKeys()
:在获取代理对象的所有属性键时触发该操作,比如在执行Object.getOwnPropertyNames(proxy)
时handler.apply()
:在调用一个目标对象为函数的代理对象时触发该操作,比如在执行proxy()
时handler.construct()
:在给一个目标对象为构造函数的代理对象构造实例时触发该操作,比如在执行new proxy()
时
Reflect
对象拥有对应的可以控制各种元编程任务的静态方法。这些功能和Proxy
一一对应。
下面的这些名称你可能看起来很眼熟(因为他们也是Object
上的方法):
Reflect.getOwnPropertyDescriptor(..)
Reflect.defineProperty(..)
Reflect.getPrototypeOf(..)
Reflect.setPrototypeOf(..)
Reflect.preventExtensions(..)
Reflect.isExtensible(..)
这些方法和在Object
上的同名方法一样。然后,一个区别在于,Object
上这么方法的第一个参数是一个对象,Reflect
遇到这种情况会扔出一个错误。
如果handler
没有设置任何拦截,那就等同于直接通向原对象。
let target = {}
let handler = {}
let proxy = new Proxy(target, handler)
proxy.name = '大漠'
console.log(`Proxy: ${proxy.name}`) // => Proxy: 大漠
console.log(`Target: ${target.name}`) // => Target: 大漠
上面代码中,handler
是一个空对象,没有任何拦截效果,访问handler
就等同于访问target
。
Proxy
支持的拦截操作
handler.get()
get(target, propKey, receiver)
:拦截对象属性的读取,比如proxy.foo
和proxy['foo']
、Reflect.get(...)
,返回类型不限。最后一个参数receiver
可选,当target
对象设置了propKey
属性的get
函数时,receiver
对象会绑定get
函数的this
对象
let person = {
name: '大漠'
}
let handler = {
get: function (target, propKey, receiver) {
if (propKey in target) {
return target[propKey]
} else {
throw new ReferenceError(`Property ${propKey} does not exist!`)
}
}
}
let proxy = new Proxy(person, handler)
console.log(proxy.name)
console.log(proxy.age)
Image may be NSFW.
Clik here to view.
上面的代码表示,如果访问目标对象不存在的属性,会抛出一个错误。如果没有这个拦截函数,访问不存在的属性,只会返回undefined
。
get()
方法是可以继承的。
let proxy = new Proxy({},{
get(target, propKey, receiver) {
console.log(`GET: ${propKey}`)
return target[propKey]
}
})
let obj = Object.create(proxy)
console.log(obj.name)
Image may be NSFW.
Clik here to view.
上面代码中,拦截操作定义在Prototype
对象上面,所以如果读取obj
对象继承的属性时,拦截会生效。
下面的例子,使用get
拦截,实现数组读取负数的索引。
function createArray(...items) {
let handler = {
get(target, propKey, receiver) {
let index = parseInt(propKey)
if (index < 0) {
propKey = String(target.length + index)
}
return Reflect.get(target, propKey, receiver)
}
}
let target = []
target.push(...items)
return new Proxy(target, handler)
}
let array = createArray('大漠', 'w3cplus.com', '切图仔')
console.log(array[-1]) // => 切图仔
上面代码中,数组的位置参数是-1
,就会输出数组的倒数第一个item
值,也就是切图仔
。
handler.set()
set(target, propKey, value, receiver)
用来拦截对象属性的设置,比如proxy.foo = v
或proxy['foo'] = v
、Reflect.set(...)
,返回一个布尔值。
假定Person
对象有一个age
属性,该属性应该是一个不大于100
的整数,那么可以使用Proxy
对象保证age
的属性值符合要求。
let validator = {
set: function (target, propKey, value, receiver) {
if (propKey === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('age不是一个整数')
}
if (value > 100) {
throw new RangeError('age不是有效值')
}
}
// 对于age以外的属性,直接保存
target[propKey] = value
}
}
let Person = new Proxy({}, validator)
Person.age = 36
Person.name = '大漠'
console.log(Person.age) // => 36
console.log(Person.name) // => 大漠
Person.age = 200
console.log(Person.age) // => age不是有效值
Image may be NSFW.
Clik here to view.
上面的示例中,由于设置了存值函数set()
,任何不符合要求的age
属性赋值,都会抛出一个错误。利用set()
方法,还可以数据绑定,即每当对象发生变化时,会自动更新。在使用JavaScript对表单进行验证时,非常有用。
利用proxy
拦截不符合要求的数据
function validator(target, validator, errorMsg) {
return new Proxy(target, {
_validator: validator,
set(target, key, value, proxy) {
let errMsg = errorMsg
if (value == '') {
alert(`${errMsg[key]}不能为空!`)
return target[key] = false
}
let va = this._validator[key]
if (!!va(value)) {
return Reflect.set(target, key, value, proxy)
} else {
alert(`${errMsg[key]}格式不正确`)
return target[key] = false
}
}
})
}
负责校验的逻辑代码
const validators = {
name(value) {
return value.length > 6
},
passwd(value) {
return value.length > 6
},
moblie(value) {
return /^1(3|5|7|8|9)[0-9]{9}$/.test(value)
},
email(value) {
return /^\w+([+-.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/.test(value)
}
}
客户端调用代码
const errorMsg = {
name: '用户名',
passwd: '密码',
moblie: '手机号码',
email: '邮箱地址'
}
const vali = validator({}, validators, errorMsg)
let registerForm = document.querySelector('#registerForm')
registerForm.addEventListener(
'submit',
function() {
let validatorNext = function*() {
yield vali.name = registerForm.userName.value
yield vali.passwd = registerForm.passWord.value
yield vali.moblie = registerForm.phoneNumber.value
yield vali.email = registerForm.emailAddress.value
}
let validator = validatorNext()
validator.next();
!vali.name || validator.next(); //上一步的校验通过才执行下一步
!vali.passwd || validator.next();
!vali.moblie || validator.next();
},
false)
上面这部分验证表单的代码来自于@jawil的《探索两种优雅的表单验证》
handler.has()
has(target, propKey)
用来拦截propKey in proxy
、Reflect.has(...)
的操作,返回一个布尔值。
has()
方法可以隐藏某些属性,不被in
操作符发现。
let handler = {
has: function(target, key) {
if (key[0] === '_') {
return false
}
return key in target
}
}
let target = {
_prop: '大漠',
prop: 'W3cplus'
}
let proxy = new Proxy(target, handler)
console.log('_prop' in proxy) // => false
console.log('prop' in proxy) // => true
如果原对象的属性名的第一个字符是下划线,proxy.has
就会返回false
,从而不会被in
运算符发现。如果原对象不可配置或者禁止扩展,这个时候,has()
拦截就会报错。
let target = {
age: 30,
name: '大漠'
}
Object.preventExtensions(target)
let handler = {
has: function (target, propKey) {
return false
}
}
let proxy = new Proxy(target, handler)
console.log(proxy.age)
console.log(proxy.name)
console.log('age' in proxy)
Image may be NSFW.
Clik here to view.
上例中,target
对象禁止扩展,结果使用has()
拦截就会报错。
handler.construct()
construct(target, args, proxy)
用来拦截Proxy
实例作为构造函数调用的操作,比如new proxy(...args)
和Reflect.construct(...)
。
construct()
方法用来拦截new
命令。
let handler = {
construct: function (target, args) {
console.log('Called:' + args.join(','))
return {value: args[0] * 10}
}
}
let target = function (){}
let proxy = new Proxy(target, handler)
console.log(new proxy(2).value)
Image may be NSFW.
Clik here to view.
如果construct()
方法返回的不是对象,就会抛出错误。
let handler = {
construct: function (target, args) {
return 'w3cplus'
}
}
let target = function () {}
let proxy = new Proxy(target, handler)
console.log(new proxy())
Image may be NSFW.
Clik here to view.
handler.deleteProperty()
deleteProperty(target, propKey)
用于拦截delete proxy[propKey]
和Reflect.deleteProperty(...)
的操作,返回一个布尔值。如果这个方法抛出错误或者返回false
,当前属性就无法被delete
命令删除。
let target = {
_prop: '大漠',
prop: 'w3cplus'
}
let handler = {
deleteProperty: function (target, propKey) {
invariant(propKey, 'delete')
return true
}
}
function invariant(key, action) {
if (key[0] === '_') {
throw new Error(`Invalid attempt to ${action} private '${key}' property!`)
}
}
let proxy = new Proxy(target, handler)
console.log(delete proxy.prop)
console.log(delete proxy._prop)
Image may be NSFW.
Clik here to view.
上面示例中,deleteProperty()
方法拦截了delete
操作符,删除第一个字符为下划线的属性会报错。
handler.defineProperty()
defineProperty(target, propKey, propDesc)
用于拦截Object.defineProperty(proxy, propKey, propDesc)
、Object.defineProperties(proxy, propDesc)
、Reflect.defineProperty(...)
,返回一个布尔值。
let target = {}
let handler = {
defineProperty: function (target, propKey, propDescriptor) {
console.log(`Called: ${propKey}`)
return false
}
}
let proxy = new Proxy(target, handler)
let propDescriptor = {
configurable: true,
enumerable: true,
value: 10
}
Object.defineProperty(proxy, 'name', propDescriptor)
Image may be NSFW.
Clik here to view.
当调用 Object.defineProperty()
或者 Reflect.defineProperty()
,传递给 defineProperty
的 descriptor
有一个限制 - 只有以下属性才有用,非标准的属性将会被无视:
enumerable
configurable
writable
value
get
set
如果违背了以下的不变量,Proxy
会抛出TypeError
:
- 如果目标对象
target
不可扩展,将不能添加属性 - 不能添加或者修改一个属性为不可配置的,如果它不作为一个目标对象的不可配置的属性存在的话
- 如果目标对象存在一个对应的可配置属性,这个属性可能不会是不可配置的
- 如果一个属性在目标对象中存在对应的属性,那么
Object.defineProperty(target, propKey, propDescriptor)
将不会抛出异常 - 在严格模式下,
false
作为handler.defineProperty
方法的返回值的话将会抛出TypeError
异常
handler.getOwnPropertyDescriptor()
getOwnPropertyDescriptor(target, propKey)
用于拦截Object.getOwnPropertyDescriptor(proxy, propKey)
和Reflect.getOwnPropertyDescriptor(...)
,返回属性的描述对象或者undefined
。
let handler = {
getOwnPropertyDescriptor: function (target, key) {
console.log(`Called: ${key}`)
if (key[0] === '_') {
return
}
return Object.getOwnPropertyDescriptor(target, key)
}
}
let target = {
_prop: '大漠',
prop: 'w3cplus'
}
let proxy = new Proxy(target, handler)
console.log(Object.getOwnPropertyDescriptor(proxy, 'name'))
console.log(Object.getOwnPropertyDescriptor(proxy, '_prop'))
console.log(Object.getOwnPropertyDescriptor(proxy, 'prop'))
Image may be NSFW.
Clik here to view.
如果下列不变量被违反,代理将抛出一个 TypeError
:
getOwnPropertyDescriptor
必须返回一个object
或undefined
- 如果属性作为目标对象的不可配置的属性存在,则该属性无法报告为不存在。
- 如果属性作为目标对象的属性存在,并且目标对象不可扩展,则该属性无法报告为不存在
- 如果属性不存在作为目标对象的属性,并且目标对象不可扩展,则不能将其报告为存在
- 属性不能被报告为不可配置,如果它不作为目标对象的自身属性存在,或者作为目标对象的可配置的属性存在
Object.getOwnPropertyDescriptor(target)
的结果可以使用Object.defineProperty
应用于目标对象,也不会抛出异常
以下是 Object.getOwnPropertyDescriptor()
的代码违反了不变量:
let target = {
name: '大漠'
}
Object.preventExtensions(target)
let handler = {
getOwnPropertyDescriptor: function (target, propKey) {
console.log(`Called: ${propKey}`)
return undefined
}
}
let proxy = new Proxy(target, handler)
console.log(Object.getOwnPropertyDescriptor(proxy, 'name'))
Image may be NSFW.
Clik here to view.
handler.getPrototypeOf()
getPrototypeOf(target)
用于拦截Object.getPrototypeOf(proxy)
、Reflect.preventExtensions(...)
、__proto__
、Object.isPrototypeOf()
、instanceof
,返回一个对象。
let target = {}
let handler = {
getPrototypeOf: function (target) {
return Array.prototype
}
}
let proxy = new Proxy(target, handler)
console.log(
`Object.getPrototypeOf(proxy): ${Object.getPrototypeOf(proxy) === Array.prototype},
Reflect.getPrototypeOf(proxy): ${Reflect.getPrototypeOf(proxy) === Array.prototype},
proxy.__proto__: ${proxy.__proto__ === Array.prototype},
Array.prototype.isPrototypeOf(proxy): ${Array.prototype.isPrototypeOf(proxy)},
proxy instanceof Array: ${proxy instanceof Array}`)
Image may be NSFW.
Clik here to view.
如果遇到了下面两种情况,JavaScript 引擎会抛出 TypeError
异常:
getPrototypeOf()
方法返回的不是对象也不是null
- 目标对象是不可扩展的,且
getPrototypeOf()
方法返回的原型不是目标对象本身的原型
下面的示例展示的两种情况下就会报异常:
let proxy1 = new Proxy(
{},
{
getPrototypeOf: function (target) {
return '大漠'
}
}
)
console.log(Object.getPrototypeOf(proxy1))
let proxy2 = new Proxy(
Object.preventExtensions({}),
{
getPrototypeOf: function (target) {
return {}
}
}
)
console.log(Object.getPrototypeOf(proxy2))
Image may be NSFW.
Clik here to view.
handler.isExtensible()
isExtensible(target)
拦截Object.isExtensible(proxy)
和Reflect.isExtensible(...)
,返回一个布尔值。
let target = {}
let handler = {
isExtensible: function (target) {
console.log(`Called`)
return true
}
}
let proxy = new Proxy(target, handler)
console.log(Object.isExtensible(proxy))
Image may be NSFW.
Clik here to view.
这个方法有一个强限制,如果不能满足下面的条件,就会抛出错误。
Object.isExtensible(proxy) === Object.isExtensible(target)
下面是一个示例:
let target = {}
let handler = {
isExtensible: function (target) {
console.log('Call')
return false
}
}
let proxy = new Proxy(target, handler)
console.log(Object.isExtensible(proxy))
Image may be NSFW.
Clik here to view.
handler.ownKeys()
ownKeys(target)
用于拦截Object.getOwnPropertyNames(proxy)
、Object.getOwnPropertySymbols(proxy)
、Object.keys(proxy)
、Reflect.get()
和Reflect.ownKeys(...)
,返回一个数组。该方法返回对象所有自身的属性,而Object.keys()
仅返回对象可遍历的属性。
let target = {}
let handler = {
ownKeys: function (target) {
console.log('Called')
return ['a', 'b', 'c']
}
}
let proxy = new Proxy(target, handler)
console.log(Object.getOwnPropertyNames(proxy))
Image may be NSFW.
Clik here to view.
如果违反了下面的约定,Proxy
将抛出错误 TypeError
:
ownKeys
的结果必须试一个数组- 数组的元素类型要么是一个
String
,要么是一个Symbol
- 结果列表必须包含目标对象的所有不可配置(non-configurable )、自有(
own
)属性的key
- 如果目标对象不可扩展,那么结果列表必须包含目标对象的所有自有(
own
)属性的key
,不能有其它值
下面的代码违反了约定,将会抛出TypeError
:
let target = {}
Object.defineProperty(
target,
'name',
{
configurable: false,
enumerable: true,
value: 10
}
)
let handler = {
ownKeys: function (target) {
return [123, 12.5, true, false, undefined, null, {}, []]
}
}
let proxy = new Proxy(target, handler)
console.log(Object.getOwnPropertyName(proxy))
Image may be NSFW.
Clik here to view.
handler.preventExtensions()
preventExtensions(target)
用于拦截Object.preventExtensions(proxy)
和Reflect.preventExtensions(...)
,将返回一个布尔值。这个方法有一个限制,只有当Object.isExtensible(proxy)
为false
(即不可扩展)时,proxy.preventExtensions
才能返回true
,否则会报错。
let target = {}
let handler = {
preventExtensions: function(target) {
return true
}
}
let proxy = new Proxy(target, handler)
console.log(Object.preventExtensions(proxy))
Image may be NSFW.
Clik here to view.
上面的代码,proxy.preventExtensions
方法返回true
,但这时Object.isExtensible(proxy)
会返回true
,因此报错。为了防止出现这个问题,通常要在proxy.preventExtensions
方法里面,调用一次Object.preventExtensions
。
let target = {}
let handler = {
preventExtensions: function (target) {
console.log('Called')
Object.preventExtensions(target)
return true
}
}
let proxy = new Proxy (target, handler)
console.log(Object.preventExtensions(proxy))
Image may be NSFW.
Clik here to view.
handler.setPrototypeOf()
setPrototypeOf(target, proto)
用于拦截Object.setPrototypeOf(proxy, proto)
和Reflect.setPrototypeOf(...)
。将返回一个布尔值。
let target = function () {}
let handler = {
setPrototypeOf: function(target, propKey) {
throw new Error('Changing the prototype is forbidden')
}
}
let propKey = {}
let proxy = new Proxy(target, handler)
console.log(proxy.setPrototypeOf(proxy, propKey))
Image may be NSFW.
Clik here to view.
handler.apply()
apply(target, object, args)
用于拦截Proxy
实例作为函数调用的操作,比如proxy(...args)
、proxy.call(object, ...args)
、proxy.apply(...)
和Reflect.apply(...)
。
let target = function () {}
let handler = {
apply: function (target, thisArg, argumentsList) {
console.log(`Called: ${argumentsList.join(',')}`)
return argumentsList[0] + argumentsList[1] + argumentsList[2]
}
}
let proxy = new Proxy (target, handler)
console.log(proxy(1, 2, 3))
Image may be NSFW.
Clik here to view.
proxy.revocable()
Proxy
的revocable
的意思是,取消访问proxy
的权限. 这个有什么用呢? 没有什么卵用… 开玩笑的. 他的映射范围应该算是比较少的。但是往往却是精华. 现在,我们有这样一个场景,假如,你现在有一个敏感数据, 但如果突然发生错误,你想立即取消掉对该数据的访问,so? 一般,要不就是直接删除该数据。 但这样方式,太简单粗暴了。 而,proxy
提供了一个完美的trick
来帮助我们实现这一方式。使用revocable()
方法. 这时, 定义一个proxy
就需要使用 Proxy.revocable(target, handler)
。
let target = {};
let handler = {};
let {proxy, revoke} = Proxy.revocable(target, handler);
proxy.foo = 123;
proxy.foo // 123
revoke();
proxy.foo // TypeError: Revoked
Proxy.revocable
方法返回一个对象,该对象的proxy
属性是Proxy
实例,revoke
属性是一个函数,可以取消Proxy
实例。上面代码中,当执行revoke
函数之后,再访问Proxy
实例,就会抛出一个错误。
Reflect
Reflect
对象与Proxy
对象一样,也是ES6为了操作对象而提供的新API。Reflect
对象的设计目的有这样几个。
- 将
Object
对象的一些明显属于语言内部的方法(比如Object.defineProperty
),放到Reflect
对象上。现阶段,某些方法同时在Object
和Reflect
对象上部署,未来的新方法将只部署在Reflect
对象上。 - 修改某些
Object
方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)
在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)
则会返回false
。 - 让
Object
操作都变成函数行为。某些Object
操作是命令式,比如name in obj
和delete obj[name]
,而Reflect.has(obj, name)
和Reflect.deleteProperty(obj, name)
让它们变成了函数行为。 Reflect
对象的方法与Proxy
对象的方法一一对应,只要是Proxy
对象的方法,就能在Reflect
对象上找到对应的方法。这就让Proxy
对象可以方便地调用对应的Reflect
方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy
怎么修改默认行为,你总可以在Reflect
上获取默认行为。
一个对象的键们可以这样被访问:
Reflect.ownKeys(..)
:返回对象自己的键(不是通过“继承”的),同样也返回Object.getOwnPropertyNames()
和Object.getOwnPropertySymbols()
Reflect.enumerate(..)
:返回对象上可枚举的属性(包括“继承”过来的)。Reflect.has(..)
:和in
操作符差不多。
函数的调用和构造调用可以手动通过下面 API 执行:
Reflect.apply(..)
:举个例子,Reflect.apply(foo,thisObj,[42,"bar"])
调用了foo()
函数,上下文是thisObj
,参数为42
和bar
。Reflect.construct(..)
:举个例子,Reflect.construct(foo,[42,"bar"])
等于new foo(42,"bar")
。
对象属性访问,设置,删除可以手动通过下面 API 执行:
Reflect.get()
:Reflect.get(o,"foo")
等于o.foo
。Reflect.set()
:Reflect.set(o,"foo",42)
等于o.foo = 42
。Reflect.deleteProperty()
:Reflect.deleteProperty(o,"foo")
等于delete o.foo
。
Reflect
的元编程能力可以让你等效的模拟以前隐藏的各种语法特性。这样你就可以使用这些功能为特定语言(DSL)拓展新功能和 API。
总结
这篇文章主要整理了有关于JavaScript中的Proxy
和Reflect
相关的知识点。整理的有点混乱,如果你有更好的经验或者说文章中有不对之处,还请在下面的评论中与我一起分享。
如需转载,烦请注明出处:https://www.w3cplus.com/javascript/es6-proxy-reflect.html