?本文為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)=>{// 返回promisedom.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++;
// }
// })()