Proxy 是 ES6 中新增的功能,它可以用来自定义对象中的操作。Vue3.0 中将会通过 Proxy 来替换原本的 Object.defineProperty 来实现数据响应式。
语法
let p = new Proxy(target, handler)
解释
参数 | 含义 | 必选 |
---|---|---|
target | 用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理) | Y |
handler | 一个对象,其属性是当执行一个操作时定义代理的行为的函数 | Y |
// 读法示例
let o = {
name: 'lee',
price: 190
}
let d = new Proxy(o, {
get(target, key) {
if (key === 'price') {
return target[key] + 20
} else {
return target[key]
}
}
})
console.log(d.price, d.name) // 210 "lee"
场景一:读写操作拦截
// 场景一:读写操作拦截-Proxy
let o = {
name: 'lee',
price: 190
}
// 中介函数不直接暴露o
let d = new Proxy(o, {
get(target, key) { // 读操作拦截-可读
return target[key]
},
set(target, key, value) { // 写操作拦截-不可写
return false
}
})
d.price = 300 // 赋值操作被拦截
console.log(d.price, d.name) // 190 "lee"
// 场景一:读写操作拦截-ES5 中 Proxy 的实现过程
for (let [key] of Object.entries(o)) { // Object.entries(o) 可o转换为键值对形式
// 属性描述符
Object.defineProperty(o, key, {
writable: false
})
}
o.price = 300 // 不可写
console.log(o.name, o.price) // lee 190
es5的实现方式和ES6的Proxy区别:ES5写法key被彻底锁死,不可写,只有通过再次修改属性描述符才可以修改属性值。而ES6的代理从用户角度来说是锁死的,但中介函数还可以操作。
场景二:用户交互操作,较验数据保护,数据规范
// 业务场景描述:1、不能修改数据结构,拦截无效数据 2、只能修改price且不能超过300
let o = {
name: 'lee',
price: 190
}
let d = new Proxy(o, {
get(target, key) {
return target[key] || '' // 异常处理
},
set(target, key, value) {
if (Reflect.has(target, key)) {
if (key === 'price') {
if (value > 300) { // 大于300 不允许写操作
return false
} else {
target[key] = value
}
}
} else { // 操作属性不是 price 不允许写操作
return false
}
}
})
// d.price = 280
// console.log(d.price, d.name) // 280 "lee"
// d.price = 310
// console.log(d.price, d.name) // 190 "lee"
// d.price = 301
// d.name = 'chris'
// console.log(d.price, d.name) // 190 "lee"
d.age = 3
console.log(d.price, d.name, d.age) // 190 "lee" ""
// 上述功能模块化/解耦
let o = {
name: 'lee',
price: 190
}
let validator = (target, key, value) => {
if (Reflect.has(target, key)) {
if (key === 'price') {
if (value > 300) { // 大于300 不允许写操作
return false
} else {
target[key] = value
}
}
} else { // 操作属性不是 price 不允许写操作
return false
}
}
let d = new Proxy(o, {
get(target, key) {
return target[key] || '' // 异常处理
},
set: validator
})
// d.price = 280
// console.log(d.price, d.name) // 280 "lee"
// d.price = 310
// console.log(d.price, d.name) // 190 "lee"
// d.price = 301
// d.name = 'chris'
// console.log(d.price, d.name) // 190 "lee"
d.age = 3
console.log(d.price, d.name, d.age) // 190 "lee" ""
场景三:用户异常行为监控、上报
let validator = (target, key, value) => {
if (Reflect.has(target, key)) {
if (key === 'price') {
if (value > 300) { // 大于300 不允许写操作
throw new TypeError('price exceed 300') // 不满足规则,触发错误
// return false
} else {
target[key] = value
}
}
} else { // 操作属性不是 price 不允许写操作
return false
}
}
let d = new Proxy(o, {
get(target, key) {
return target[key] || '' // 异常处理
},
set: validator
})
d.price = 310
console.log(d.price, d.name, d.age) // Uncaught TypeError: price exceed 300
场景四:组件化开发中异常组件瞄准
业务场景描述:声明一个类,每次生成实例都分配id,要求:每次生成的实例、不同实例之间的id随机且唯一,只读。
class Component {
constructor() {
this.proxy = new Proxy({
id: Math.random().toString(36).slice(-8)
}, {}) //生成一个随机串转换成36进制的字符串,并截止后八位
}
get id() {
return this.proxy.id
}
}
let com = new Component()
let com2 = new Component()
for (let i = 0; i < 10; i++) { // 读取十次实例对象的id
console.log(com.id, com2.id) // dmi2frvf tkk9v1qh
}
com.id = 'abc';
console.log(com.id, com2.id) // dmi2frvf tkk9v1qh
撤销代理:
创建一个临时代理或是可撤销的代理。返回的是代理数据和撤销的操作两部分内容。
let o = {
name: 'lee',
price: 190
}
// new Proxy()返回的数据是被代理的数据
// Proxy.revocable() 创建一个临时代理或是可撤销的代理。返回的是代理数据和撤销的操作两部分内容
let d = Proxy.revocable(o, {
get(target, key) {
if (key === 'price') {
return target[key] + 20
} else {
return target[key]
}
}
})
console.log(d.proxy.price, d)
setTimeout(function () {
d.revoke()
setTimeout(function () {
console.log(d.proxy.price, d) // Uncaught TypeError: Cannot perform 'get' on a proxy that has been revoked
}, 100)
}, 1000)
思考:
1、组件初始化的时候都赋值一个可读且随机的ID,该怎么做?
2、临时代理有哪些应用场景呢?
3、如何把接口的数据用代理进行包装?