关于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){}来捕获。

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