關於JS的事件機制、EventLoop、線程模型

事件機制分爲:事件捕獲階段、目標階段和冒泡階段。

冒泡是指一個事件的目標由裏層向外層冒泡,以點擊事件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){}來捕獲。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章