反射与代理
认识反射和代理
反射和代理是ECMAScript 6新增的语言特性,代理(Proxy)可以为开发者提供可拦截并且向基本操作嵌入额外的行为能力。 简单的说,在对一个代理对象访问属性、删除属性、添加属性等操作时进行拦截,并且加上自定义操作行为。反射(Reflect)是一个内置对象, 是给底层操作提供默认行为的方法的集合。它与代理的捕获器一一对应,主要功能之一为了方便Proxy捕获器调用。
代理和反射还很新,所以这边"蛮"啰嗦,如果你熟悉代理这一特性可跳过基础,应用可能对您有用。
下面看一段简单的代理:
const target = {}
const handle = {
get: function(target,key, receiver){ // 这里定义一个捕获器
console.log(target, key, receiver)
}
}
const proxy = new Proxy(target, handle);
console.log(target.a)
console.log(proxy.a)
值得注意的是,proxy对象是没有prototype的。然后可以利用反射来优化一下:
const target = {
id: 'foo'
}
const handle = {
get(){
return Reflect.get(...arguments)
}
}
const proxy = new Proxy(target, handle)
console.log(proxy.id)
前面也说到了代理的捕获器与反射的方法一一对应的,捕获器全部默认还可以这么写:
const target = {
id: 'foo'
}
const proxy = new Proxy(target, Reflect)
console.log(Reflect)
console.log(proxy.id)
普通代理对象通过new Proxy()
去创建一个代理对象,这种创建方法不能取消代理。proxy提供revocable方法,通过该方法创建的代理,
可以主动取消代理对象和目标对象的代理关联。可撤销代理:
const target = {
id: 'foo'
}
const handle = {
get(){
return Reflect.get(...arguments)
}
}
const {proxy, revoke} = Proxy.revocable(target, handle)
console.log(proxy.id)
revoke()
console.log(proxy.id) // Uncaught TypeError: Cannot perform 'get' on a proxy that has been revoked
代理捕获器和反射API
代理一共有13个捕获器(有人叫它陷阱函数),对应也有13个反射方法。如下:
- 基础捕获器
const proxy = new Proxy(target, {
/**
* @param target 目标对象
* @param p propertyKey 比如id
* @param receiver 代理对象
* @returns {任意返回值}
*/
get(target, p, receiver) {
return Reflect.get(...arguments)
},
/**
*
* @param target 目标对象
* @param p propertyKey
* @param value
* @param receiver 代理对象
* @returns {boolean}
*/
set(target, p, value, receiver) {
return Reflect.set(...arguments)
},
deleteProperty(target, p) {
console.log(...arguments)
return Reflect.deleteProperty(...arguments)
}
});
proxy.id
proxy.id = 'yosun'
delete proxy.id
- 原型代理
const target = {id: 'yosun'}
const proxy = new Proxy(target, {
/**
* @param target
* @returns {object}
*/
getPrototypeOf(target) {
console.log(...arguments)
return Reflect.getPrototypeOf(...arguments)
},
/**
* @param target
* @param v
* @returns {boolean}
*/
setPrototypeOf(target, v) {
return Reflect.setPrototypeOf(...arguments)
}
})
Object.getPrototypeOf(proxy);
Object.setPrototypeOf(proxy, {});
- 对象
const target = {}
const proxy = new Proxy(target, {
isExtensible(target){
return Reflect.isExtensible(...arguments)
},
preventExtensions(target) {
return Reflect.preventExtensions(...arguments)
},
ownKeys(target) {
console.log(...arguments)
return Reflect.ownKeys(...arguments)
},
/**
* @param target
* @param p propertyKey
* @returns {boolean}
*/
has(target, p) {
return Reflect.has(...arguments)
},
defineProperty(target, p, attributes) {
console.log(target, p, attributes)
return Reflect.defineProperty(...arguments)
},
getOwnPropertyDescriptor(target, p) {
console.log(...arguments)
// return Reflect.getOwnPropertyDescriptor(...arguments) // 使用默认
return { configurable: true, enumerable: true, value: 'sun' }
}
})
Object.isExtensible(proxy)
Object.preventExtensions(proxy)
'yosun' in target
Object.keys(proxy)
Object.defineProperty(proxy, 'id', {
value: 'yosun'
})
console.log(Object.getOwnPropertyDescriptor(proxy, 'id').value)
- 函数
const target = function () {}
const proxy = new Proxy(target, {
/**
* @param target
* @param thisArg
* @param argArray
* @returns {*}
*/
apply(target, thisArg, argArray) {
console.log(...arguments)
return Reflect.apply(...arguments)
},
/**
* @param target
* @param argArray
* @param newTarget
* @returns {any}
*/
construct(target, argArray, newTarget) {
console.log(...arguments)
return Reflect.construct(...arguments)
}
})
proxy(111)
new proxy(111)
应用
一般想禁止访问对象的一些属性,我们可以这么做:
const hiddenPropertyKey = ['sex', 'age'];
const person = {
name: 'yosun',
age: '26',
sex: 'man'
}
const findHumanInfo = new Proxy(person, {
get(target, p, receiver) {
if (!hiddenPropertyKey.includes[p]){
console.log('您还没有还没有权限访问~')
return undefined
}
return Reflect.get(...arguments)
},
has(target, p) {
if (!hiddenPropertyKey.includes[p]){
console.log('您还没有还没有权限访问~')
return false
}
return Reflect.has(...arguments)
}
})
findHumanInfo.age // 您还没有还没有权限访问
findHumanInfo.name // yosun
'age' in findHumanInfo // 您还没有还没有权限访问~ return false
我们常见的web表单验证,是个比较麻烦的问题,一般代码不容易整合。下面是set捕获器一种常见用法:
const form = new Map();
const validator = {
set(target, property, value, receiver) {
if(form.has(property)){
return form.get(property)(value)
}
return Reflect.set(...arguments)
}
}
form.set('age', validateAge)
function validateAge(value){
if(typeof value !=="number" || Number.isNaN(value)){
throw new TypeError('年龄必须是数字类型')
}
if(value<=0){
throw new TypeError('年龄必须大于零')
}
return true
}
const formValidator = {
}
const proxy = new Proxy(formValidator, validator)
proxy.age = '十八'
construct捕获器应用:
class SelfUser{
constructor(id) {
this.is = id
}
}
const User = new Proxy(SelfUser, {
construct(target, argArray, newTarget) {
if (!argArray[0]){
throw new Error("用户id不能为空")
}else {
console.log('创建用户成功')
return Reflect.construct(...arguments)
}
}
})
new User(1) // 创建用户成功
new User() // Uncaught Error: 用户id不能为空
TODO 时间原因还有很多未整理-----(自己还没理解的就先不分享)
不过还是要分析一个我觉得比较有趣的代码
const watchJsRun = (expectObject) => {
if (typeof expectObject === 'object' || typeof expectObject === 'function'){
return new Proxy(expectObject,Reflect.ownKeys(Reflect).reduce((handles, key)=> {
handles[key] = (...args)=> {
console.log(key, ...args)
return Reflect[key](...args)
}
return handles
}, {}))
}
throw new Error('Cannot create proxy with a non-object as target or handler')
}
const arr = watchJsRun([])
// console.log(arr)
arr.push(1)
console.log('------------分割线--------------')
let obj = watchJsRun({})
obj.id
console.log('------------分割线--------------')
let fn = watchJsRun(function Person() {
})
new fn()
fn()
fn.prototype.doEveryThing = function () {
console.log('doEveryThing')
}
以push为例:
get Array(1)0: 1length: 1__proto__: Array(0) push Proxy
get Array(1)0: 1length: 1__proto__: Array(0) length Proxy
set Array(1) 0 1 Proxy
getOwnPropertyDescriptor Array(1) 0
defineProperty Array(1) 0 Object
set Array(1) length 1 Proxy
getOwnPropertyDescriptor Array(1) length
defineProperty Array(1) length Object
一共执行了以上步骤。大家可以自己试试~ 上面的demo很关键,意义深远。既然看到这里了,我相信也会去尝试一下。在写到这里时候,已经是6点07分,过去下班已经7分钟 ;)。我想就算草草结束也要说明一下。这位朋友,你的点赞是我学习的动力! 哈哈哈。。。。。
常见的业务代码几乎看不到代理和反射的存在,proxy对代理的对象进行了衍生,符合低耦合,高内聚的设计理念。当然不要盲目的使用代理, 只有当对象功能变得异常复杂,我们要对访问进行限制时,再考虑使用代理。
还有很多没学会,欢迎指正~