Proxy
一、什么是代理?
代理(Proxy)是我们通过代理控制对另一个对象的访问。通过代理可以定义当对象发生交互时可执行的自定义行为——如读取或设置属性值,或调用方法。可以将代理理解为通用化的setter与getter,区别是每个setter与getter仅能控制单个对象属性,而代理可用于对象交互的通用处理,包括调用对象的方法。
使用代理,我们可以很容易地在代码中添加分析和性能度量;自动填充对象属性以避免讨厌的null异常;包装宿主对象,例如DOM用于减少跨浏览器的不兼容性。
二、怎么创建代理(Proxy)?
通过Proxy的构造器来创建一个代理,像下面这样:
let obj = { name: 'Jack' }
let proxy = new Proxy(obj, {})
上面的代码就创建了一个代理对象proxy,Proxy构造器接收两个参数,
- 第一个参数表示要代理哪个对象,
- 第二个参数是一个对象,表示的是这个代理能够做什么,比如校验,日志记录等等
那么创建好代理对象之后,能做什么呢?下面就来看看Proxy的应用。
三、代理(Proxy)的应用
3.1、使用代理记录日志(代理对象)
代理的直接用途之一是在我们读写属性时使用一种更好的、更清洁的方式启用日志记录。
使用代理易于在对象上添加日志,看下面的代码:
function makeLogger(target) {
// 定义形参为 target 的函数,并使得 target 可以记录日志
return new Proxy(target, {
// 针对 target 对象创建代理
get: (target, key) => {
report('Reading ' + key) // 通过 get 方法实现属性读取时的记录日志
return target[key]
},
set: (target, key, value) => {
report('Writing value ' + value + ' to ' + key) // 通过 set 方法实现属性赋值时的记录日志
target[key] = value
},
})
}
let obj = { name: 'carson' }
obj = makeLogger(obj) // 把 obj 作为目标对象传入 makeLogger 方法,使其可以记录日志
定义makeLogger函数,使用target对象作为形参,返回一个新的代理对象,该代理对象具有get和set方法。get和set方法会在读取对象属性时记录日志。
3.2、使用代理检测性能(代理函数)
先看下面的代码:
function isPrime(num) {
if (num < 2) {
return false
}
for (let i = 2; i < num; i++) {
if (num % i === 0) {
return false
}
}
return true
}
isPrime = new Proxy(isPrime, {
// 使用代理包装 isPrime 方法
apply: (target, thisArgs, args) => {
// 定义 apply 方法,当代理对象作为函数调用时将会触发该 apply 方法的执行
console.log(target, thisArgs, args)
console.time('isPrime')
const result = target.apply(thisArgs, args) // 调用目标函数
console.timeEnd('isPrime')
return result
},
})
isPrime(1299827) // 同调用原始方法一样,调用 isPrime 方法
使用isPrime函数作为代理的目标对象。同时,添加apply方法,当调用isPrime函数时就会调用apply方法。
3.3、使用代理实现负数组索引(代理数组)
JavaScript不支持数组负索引,但是,我们可以使用代理进行模拟,看下面的代码:
const array = ['jack', 'tom', 'dev']
function createArrayProxy(array) {
if (!Array.isArray(array)) {
throw new TypeError('array is not a array')
}
return new Proxy(array, {
// 返回新的代理,该代理使用传入的数组作为代理目标
get: (target, index) => {
// 当读取数组元素时调用 get 方法
index = +index
// 如果访问的是负向索引,则逆向访问数组,如果访问的是正向索引,则正常访问数组
return target[index < 0 ? target.length + index : index]
},
set: (target, index, value) => {
// 当写入数组元素时调用 get 方法
index = +index
target[index < 0 ? target.length + index : index] = val
},
})
}
const arrayProxy = createArrayProxy(array)
arrayProxy[-1]
创建并返回代理,该代理具有读取数组元素时将被调用的get方法,以及写入数组元素时被调用的set方法。
四、总结
使用代理可以优雅地实现以下内容。
- 日志记录。
- 性能测量。
- 数据校验。
- 自动填充对象属性(以此避免讨厌的null异常)。
- 数组负索引。
但是,代理效率不高,所以在需要执行多次的代码中需要谨慎使用。建议进行性能测试。