事件机制分为:事件捕获阶段、目标阶段和冒泡阶段。
冒泡是指一个事件的目标由里层向外层冒泡,以点击事件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){}来捕获。