「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战」。
一、代理Proxy
1.1 什么是代理
通俗理解就是:比如说我们进入公司时,公司都有门禁,我们进入公司时,需要刷我们的工卡,当我们出去的时候,我们需要按一下门禁才能出去,我们在进入和出去的时候,分别进行了刷卡和按开关的操作,这一切都发生在门禁的基础之下。我们可以理解为门禁就是代理。
在代码层面,比如说一个对象内的一个属性,当我们读或写的时候,我们可以用代理来过滤我们的操作。并不是所有的人都有读的权限,也不是所有的写入都合情合理。
代理的目的就是为其它对象提供一种代理来控制对这个对象的访问。
1.2 Object.defineProperty()
Object.defineProperty()会直接在对象上定义一个新的属性,或者修改对象的现有属性,并返回此对象。ES5中,我们可以把它看做一个代理,来过滤拦截我们对对象的操作。下面得代码我们就实现了拦截的set和get方法。但是,下面得代码却存在一个问题,那就是栈溢出。set方法调用时,会出现死循环。
let obj = {}
Object.defineProperty(obj, 'name', {
get() {
return 'hello'
},
set(val) {
this.name = val
}
})
console.log(obj.name) //hello
obj.name = 'Hi'
console.log(obj.name)
遇上上述情况,我们可以通过设置一个新的变量就可以。这是很多新手容易犯的错误。
let obj = {}
let newVal;
Object.defineProperty(obj, 'name', {
get() {
return newVal
},
set(val) {
newVal = val
}
})
obj.name = 'Hi'
console.log(obj.name) //Hi
1.3 Proxy基本写法
new Proxy()的第一个参数就是我们想要代理或者包装的对象,第二个参数{}内可以是一些拦截操作的钩子函数。
let obj = {}
let p = new Proxy(obj,{})
obj.name = "张三"
console.log(obj.name) //张三
1.4 Proxy设置get钩子
可以从如下代码中看出。get的第一个参数target就是我们传入的数组,第二个参数prop对应的就是我们传入的数组下标。当我们想要获取数组的值得时候,会自动的调用get方法,实现过滤拦截
let arr = [1,2,3]
arr = new Proxy(arr, {
get(target,prop) {
console.log(target,prop) // [1,2,3] '1'
return prop in target ? target[prop] : 'error'
}
})
console.log(arr[1]) //2
1.5 Proxy设置set钩子
首先,当我们每次往数组中设置值时,都会调用set方法,同时拦截非number类型的设置。 其次,我们还要知道,虽然数组被代理控制,但是数组上原来就有的方法是不受任何影响的
let arr = []
arr = new Proxy(arr, {
set(target,prop,val) {
if(typeof val === 'number') {
target[prop] = val
return true
}else {
return fasle
}
}
})
arr.push(1)
console.log(arr[0]) //1
1.6 Proxy设置has钩子
我们可以利用has钩子来判断元素的有无。当然我们这种例子都是为了简单的学会应用。
let length = {
min: 1,
max: 10
}
length = new Proxy(length, {
has(target,prop) {
return prop < target.max && prop > target.min
}
})
console.log(5 in length); //true
console.log(0 in length); //false
1.7 Proxy设置ownkeys钩子
ownkeys用于对象循环遍历的时候进行拦截。下面得代码拦截以_开头的属性
let user = {
name: '张三',
age: 20,
_sex: 'male'
}
user = new Proxy(user, {
ownKeys(target) {
return Object.keys(target).filter( key => !key.startsWith('_'))
}
})
for(let item in user) {
console.log(item) //name age
}
1.8 Proxy设置delProperty钩子
用来拦截判断该属性是否可以被删除
let user = {
name: '张三',
age: 20,
_sex: 'male'
}
user = new Proxy(user, {
delProperty(target,prop) {
if(prop.satrtWith('_')) {
throw new Error('这个属性不能删除')
}else {
delete target[prop]
return true
}
}
})
delete user.age
1.9 Proxy设置apply拦截函数的调用
Proxy同样可以代理函数,操作返回的结果。apply可以拦截函数的调用。apple的第一个参数就是sum函数,第二个参数是函数的上下文,第三个参数是我们需要操作的值
// delete user.age
let all = 0
let sum = (...args) => {
args.forEach(item => {
all+=item
})
return all
}
sum = new Proxy(sum, {
apply(target,ctx,args) {
return target(...args) * 2
}
})
console.log(sum(1,2,3)); //12
console.log(sum.call(null,1,2,3));
console.log(sum.apply(null,[1,2,3]));
1.10 Proxy设置construct拦截new操作
construct的第一个参数是People本身,第二个参数是new People传入的参数,第三个参数是实例对象
class People {
constructor(name,age) {
this.name = name
this.age = age
}
}
People = new Proxy(People, {
construct(target,args,newVal) {
return new target(...args)
}
})
console.log(new People('张三'));