事件機制分爲:事件捕獲階段、目標階段和冒泡階段。
冒泡是指一個事件的目標由裏層向外層冒泡,以點擊事件onclick舉例,用事件綁定onclick的寫法會出現事件冒泡,可以用事件監聽的寫法Object.addEventListener('click',(e)=>{...},boolean),第三個參數是布爾值,true爲事件捕獲,false爲事件冒泡,IE瀏覽器的兼容寫法是Object.attachEvent('onclick',(e)=>{...}),只用於事件捕獲。在小程序中內嵌bindtap可以用catchtap代替,防止事件冒泡。
阻止事件流的方法:
event.preventDefault()
:取消事件對象的默認動作以及繼續傳播。event.stopPropagation()
/ event.cancelBubble = true
:阻止事件冒泡。
利用事件冒泡的事件委託方法:
假設一個ul裏有許多個li,當每個li元素都要加上某一點擊事件的時候,寫起來比較麻煩,運行也會比較慢,因此可以利用事件委託方法:
var ul=document.querySelector("#list');
ul.addEventListener('click',e=>{
var target=e.target||e.srcElement;
if(target&&target.className.toLowerCase()==='li'){
...
}
};
可以看到冒泡路徑path是:li-ul-body-html-document-window
JS由於操作DOM和用戶交互的用途,是單線程模型的,也就是一次只能執行一個事件,這樣避免混亂的執行機制,比如添加元素的同時又刪除它引起矛盾。而單線程意味着事件和任務需要排隊進行,這就是隊列(queue),隊列是“先進先出”的執行順序,js把一些任務“掛起”,等待前面的任務執行完再回來執行“掛起”任務,所以任務分爲同步任務(synchronous)和異步任務(asynchronous),同步任務指的是在主線程上執行的任務,執行完一個執行下一個,異步任務則是在任務隊列中的某個異步線程。當主線程的堆棧爲空時將任務隊列的event推入堆棧中,處理其它消息前會先處理當前事件回調。
而對於任務隊列,它分爲宏任務(macrotasks)和微任務(microtasks),宏任務包含setTimeout,setInterval,setImmediate,UI rendering等,微任務包括promise(原生),process.nextTick,observe等,整體的script代碼相當於一個宏任務,宏任務同步執行完就執行微任務,然後再執行宏任務,如此反覆循環。
接下來看一段代碼,分析輸出順序應該是怎樣
var pro=new Promise((res,rej)=>{
console.log('promise_1');
res();
}).then(()=>{
console.log('promise_2');
})
console.log('start')
const interval = setInterval(() => {
console.log('setInterval')
}, 0)
setTimeout(() => {
console.log('setTimeout 1')
Promise.resolve()
.then(() => {
console.log('promise 3')
})
.then(() => {
console.log('promise 4')
})
.then(() => {
setTimeout(() => {
console.log('setTimeout 2')
Promise.resolve()
.then(() => {
console.log('promise 5')
})
.then(() => {
console.log('promise 6')
})
.then(() => {
clearInterval(interval)
})
}, 0)
})
}, 0)
Promise.resolve()
.then(() => {
console.log('promise 1')
})
.then(() => {
console.log('promise 2')
})
分析一下,首先執行同步任務,輸出promise_1和start,此時執行任務隊列中第一個任務,輸出promise_2,然後先執行微任務Promise,輸出promise 1和promise 2,然後是任務隊列中的interval定時器,然後是setTimeout定時器,此時interval定時器又進入任務隊列中,又一次輸出setInterval,再執行then裏的定時器,最後清除interval定時器便不再輸出setInterval
所以結果如下:
JS中的異步函數寫法有回調函數、promise函數、Generator函數,Async/await函數
回調和promise在此不做介紹。利於區分,generator函數使用function *fn(){}的星號定義方式,在語句前可以加入yield,當執行遇到yield的時候就會交出任務執行權,可以靠任務執行器fn().next執行下一步,任務流程管理顯得很不方便。
Async函數的定義方法是async function fn(){},同樣的,在語句前加入await,async 函數就是將 Generator 函數的星號(*)替換成 async,將 yield 替換成 await,但async函數自帶內置的執行器,不用藉助co函數庫來自動執行,也有更好的語義,async代表這是聲明一個異步函數,await表示緊跟其後的表達式需要等待結果,使用範圍也更廣泛,await後面可以跟promise對象和普通的類型值(此時會變成同步操作)。後面返回的promise對象可能是rejected,可以用await return_promise().catch(function (err){ console.log(err); });或用try{}catch(err){}來捕獲。