本文为proxy代理的实例应用,有关代理的内容可以参考:
js语法---理解反射Reflect对象和代理Proxy对象
监听事件
要监听dom元素的事件,我们会采用回调触发的方式来执行操作,
而触发事件的过程很明显是一个异步操作,异步操作可以使用回调执行,
除此之外,异步操作也可以使用promise来执行,
也就是说,触发一个事件就是完成一个promise,
而完成一个promise就执行一个操作,这样就完成了对一个事件的一次监听
模拟按钮点击事件
我们需要提供一个方法获取一个proxy代理对象,它会拦截事件属性,并返回一个promise,这个事件触发时,promise完成,然后由于事件是可以多次触发的(点击一次触发一次),我们就需要循环监听,每次拦截事件,都有返回一个新的promise,并等待,
(async()=>{
// 获得元素实例,
const btn = getElement('button')
while(1){//循环监听事件
// 等待事件触发
await btn.waitClick;
// 执行操作
console.log('click')
}
})()
通过getElement方法拿到元素的代理对象,然后设置一个无限循环,每次循环都要等待代理对象的waitClick属性,这个属性会返回一个promise等待点击事件的完成,当我们点击了按钮之后,执行一个操作,然后进入下一次循环,继续等待点击
实现getElement方法
const getElement = (element)=>{
const dom = document.querySelector(element);
// 使用代理拦截访问器,捕获属性
const proxy = new Proxy(dom,{
get(target,key){
if(key === 'waitClick'){//捕获waitClick属性
return new Promise((res)=>{// 返回promise
dom.addEventListener('click',res,{once:true});//当按钮监听到click事件,执行res,让promise完成,只触发一次
})
}
}
})
// 代理对象的功能: 拦截对象的访问---访问对象,访问属性,拦截对象的设置---设置属性和值,
// 拦截表示可以添加额外的处理,操作属性名,属性值
return proxy;
}
注意:每次点击按钮都只触发一次事件,完成一个promise,而下一次循环访问waitClick属性时,又会返回一个新的promise等待按钮点击,
可以看到我们点击了按钮11次,就执行了11次打印,这样就相当于onclick属性的回调,
但是不同的是,我们可以对每一次的事件触发都进行捕获,控制每一次事件的执行,
比如说控制事件总共能监听的次数,对不同的次数执行不同的操作,
(async()=>{
// 获得元素实例,
const btn = getElement('button')
let x = 0;
while(x<10){//循环监听事件
// 等待事件触发
await btn.waitClick;
// 执行操作
console.log(`第${x+1}次点击`);
x++;
}
})()
这里就只能触发10次事件,每次事件监听都可以区分开,
优化代理
上面的代理方式只能触发一个click事件,但是在dom元素中,事件是非常多的,要让它能监听多个事件,不该是去一个一个的添加属性捕获,
可以优化拦截捕获,这里采用的是wait+事件名称的属性名,只需要去将wait开头的事件名提取出来,进行监听,
const getElement = (element)=>{
const dom = document.querySelector(element);
// 使用代理拦截访问器,捕获事件
const proxy = new Proxy(dom,{
get(target,key){
if(!key.startsWith('wait')){//如果属性名没有wait开头
return Reflect.get(target,key);//返回原属性
}else{
const eventName = key.replace('wait','').toLowerCase();//去掉wait然后将属性名开头小写
return new Promise((res)=>{
dom.addEventListener(eventName,res,{once:true}) //事件触发时,promise执行成功,只触发一次
})
}
}
})
// 代理对象的功能: 拦截对象的访问---访问对象,访问属性,拦截对象的设置---设置属性和值,
// 拦截表示可以添加额外的处理,操作属性名,属性值
return proxy;
}
这里会监听所有以wait开头的属性,并且触发对应的事件
(async () => {
// 获得元素实例,
const body = getElement('body')
let x = 0;
while (x < 5) {//循环监听事件
// 等待事件触发
await body.waitKeydown;
// 执行操作
console.log(`第${x + 1}次按下键盘`);
x++;
}
})()
这里可以监听5次键盘按下,这样就实现了任意事件的监听,
tips:当然以上的操作都可以使用回调的方式监听,而且性能会高于这里的循环等待,这里只是展示proxy的用法,实际开发中事件的监听还是采用回调的方式是最优解,
完整代码展示
// 消除事件监听的回调,无限循环中,等待事件触发再执行操作
const getElement = (element) => {
const dom = document.querySelector(element);
// 使用代理拦截访问器,捕获事件
const proxy = new Proxy(dom, {
get(target, key) {
if (!key.startsWith('wait')) {//如果属性名没有wait开头
return Reflect.get(target, key);//返回原属性
} else {
const eventName = key.replace('wait', '').toLowerCase();//去掉wait然后将属性名开头小写
return new Promise((res) => {
dom.addEventListener(eventName, res, { once: true }) //事件触发时,promise执行成功,只触发一次
})
}
}
})
// 代理对象的功能: 拦截对象的访问---访问对象,访问属性,拦截对象的设置---设置属性和值,
// 拦截表示可以添加额外的处理,操作属性名,属性值
return proxy;
}
(async () => {
// 获得元素实例,
const body = getElement('body')
let x = 0;
while (x < 5) {//循环监听事件
// 等待事件触发
await body.waitKeydown;
// 执行操作
console.log(`第${x + 1}次按下键盘`);
x++;
}
})()
// const getElement = (element)=>{
// const dom = document.querySelector(element);
// // 使用代理拦截访问器,捕获属性
// const proxy = new Proxy(dom,{
// get(target,key){
// if(key === 'waitClick'){//捕获waitClick属性
// return new Promise((res)=>{// 返回promise
// dom.addEventListener('click',res,{once:true});//当按钮监听到click事件,执行res,让promise完成,只触发一次
// })
// }
// }
// })
// // 代理对象的功能: 拦截对象的访问---访问对象,访问属性,拦截对象的设置---设置属性和值,
// // 拦截表示可以添加额外的处理,操作属性名,属性值
// return proxy;
// }
// (async()=>{
// // 获得元素实例,
// const btn = getElement('button')
// let x = 0;
// while(x<10){//循环监听事件
// // 等待事件触发
// await btn.waitClick;
// // 执行操作
// console.log(`第${x+1}次点击`);
// x++;
// }
// })()