在目标对象之前架设了一层拦截,当外界访问这个对象的时候,都必须经过这层拦截,相当于是提供一个机制对于外界的访问,进行一个过滤或者改写
ES5 中的代理 defineProperty
Object.defineProperty() 方法中包含三个参数:拦截的对象,拦截的对象中的属性, 拦截的方法
let obj = {}
Object.defineProperty(obj, 'name', {
get(){
console.log('get')
return 'imooc'
},
set(){
console.log('set')
}
})
console.log('get方法', obj.name)
obj.name = 'es6'
调用set() 方法时,一定要注意,不能设置对象下已经存在的属性,这样会导致死循环,因为set()方法会一直被触发
ES6中的代理 Proxy
new Proxy() 第一个参数为包装的对象,第二个参数为存放方法的对象,主要处理过滤或者改写,即 get() / set()
let obj = {}
let p = new Proxy(obj, {})
p.name = 'imooc'
console.log(obj.name) // imooc
let arr = [7,8,9]
arr = new Proxy(arr, {
get(target, prop) {
console.log(target, prop)
// 判断传入的参数是否存在数组中,不存在返回error
return prop in target ? target[prop] : 'error'
}
})
console.log(arr[1])
console.log(arr[10])
当我们想要获取我们所代理的对象中的值时,会自动调用 proxy中设置的 get() 方法,我们可以在 get() 中写一些业务处理的方法
同样对于 对象类型数据的 代理和 数组的代理道理类似
get() 拦截属性读取的钩子函数
创建一个字典类型的业务,如果字典中有该字段对应的数据,则返回对应数据,若没有则返回输入的数据
let dict = {
'hello': '你好',
'world': '世界'
}
dict = new Proxy(dict, {
get(target, prop) {
return prop in target ? target[prop] : prop
}
})
console.log(dict['world']) // 世界
console.log(dict['imooc']) // imooc
set() 拦截属性设置的钩子函数
会返回一个布尔值, proxy.foo = v
或者 proxy['foo'] = v
的时候会触发
业务描述:一个数组中的元素只能是Number类型,设置值的时候需要进行类型检查,非Number类型,抛出错误
let arr = []
arr = new Proxy(arr, {
set(target, prop, val) {
if (typeof val == 'number') {
target[prop] = val;
return true;
} else {
return false
}
}
})
arr.push(5)
arr.push(6)
console.log(arr[0], arr[1], arr.length) // 5 6 2
使用代理方法的时候,并不会破坏目标对象原有的一些属性和方法
has() 拦截in()操作的钩子函数
判断一个key 是否存在 对象中,并返回一个 boolean 类型的值
业务场景:判断一个数据是否在某个范围内
let range = {
start: 1,
end: 5
}
range = new Proxy(range, {
has(target, prop) {
return prop >= target.start && prop <= target.end
}
})
console.log(2 in range) // true
console.log(9 in range) // false
ownKeys() 拦截循环遍历的钩子函数
Object.keys(),for … in , Object.getOwnPropertyNames(proxy) 、 Object.getOwnPropertySymbols(proxy) 这些方法时会触发, 并且返回一个数组
业务场景: 如果对象中有某个属性是私有属性,不想被外人访问
let userInfo = {
username: '潜龙',
age: 24,
_password: '***'
}
userInfo = new Proxy(userInfo, {
ownKeys(target) {
// target 表示当前被 代理的对象
// 屏蔽下划线开头的属性
return Object.keys(target).filter(key => !key.startsWith('_'))
}
})
for (let key in userInfo) {
console.log(key) // username, age
}
console.log(Object.keys(userInfo)) // (2) ["username", "age"]
deleteProperty() 拦截删除的钩子函数
let user = {
name: '潜龙',
age: 24,
_password: '***'
}
user = new Proxy(user, {
deleteProperty(target, prop) {
if (prop.startsWith('_')) {
throw new Error('不可删除')
} else {
delete target[prop]
return true
}
}
})
try{
delete user.age
delete user._password
}catch (e){
console.log(e.message) // 不可删除
}
console.log(user) // Proxy{name: "潜龙", _password: "***"}
apply() 拦截函数调用的钩子函数
函数的 方法调用,以及 call() 和 apply() 方法都会触发,这个钩子函数
可以用这个钩子函数来修改函数的一些返回值
let sum = (...args) => {
let num = 0
args.forEach(item => {
num += item
});
return num
};
sum = new Proxy(sum, {
// 目标对象, 上下文, 参数数组
apply(target, ctx, args) {
return target(...args) * 2
}
})
console.log(sum(1, 2)); // 6
console.log(sum.call(null, 1, 2, 3)) // 12
console.log(sum.apply(null,[ 1, 2, 3])) // 12
construct() 拦截new命令的钩子函数
主要处理,实例化对象过程中的事务, 并且这个钩子函数必须要返回一个对象
let User = class {
constructor(name) {
this.name = name
}
}
User = new Proxy(User, {
// proxy包裹的对象,传入的参数数组,new实例化的对象
construct(target, argArray, newTarget) {
console.log('construct')
return new target(...argArray)
}
})
console.log(new User('imooc')) // User{name: "imooc"}
综合应用
get() 配合私有属性
let user = {
name: '潜龙',
age: 24,
_password: '***'
}
user = new Proxy(user, {
get(target, prop) {
if (prop.startsWith("_")) {
throw new Error('不可访问')
}else{
return target[prop]
}
}
})
console.log(user.age);
console.log(user._password);
set() 配合私有属性
let user = {
name: '潜龙',
age: 24,
_password: '***'
}
user = new Proxy(user, {
set(target, prop, val) {
if (prop.startsWith('_')) {
throw new Error('不可访问');
} else {
target[prop] = val
return true
}
}
})
user.age = 25
console.log(user.age)
try {
user._password = '----'
} catch (error){
console.log(error.message) // 不可访问
}