Js事件及机制
Js事件及机制
1、事件模型
基本事件模型:也称dom0事件模型,是浏览器初期出现的一种比较简单的事件模型,主要通过事件属性,为指定标签绑定事件处理函数。由于这种模式应用类型比较广泛。获得了所有浏览器的支持,目前依然比较流行。但是这种模型对html标签依赖严重,不利于JavaScript独立开发。
dom事件模型:由w3c制定,是目前标准的事件处理模型。所有符合标准的浏览器,ie怪异模式不支持。dom事件模型包括dom和dom3事件模块.
ie事件模型:IE4.0及以上版本浏览器支持。与dom事件类似,但用法不同
Netscape事件模型:由netScape 4 浏览器实现,在netScape 6停止支持。
事件流
1、冒泡型
微软提出了名为事件冒泡的事件流。事件按照从最特定的事件目标到最不特定的事件目标(document对象)的顺序触发。可以想象把一颗石头投入水中,泡泡会一直从水底冒出水面。也就是说,事件会从最内层的元素开始发生,一直向上传播,直到document对象。
<div id="dv1" style="width:300px;height:300px;
<div id="dv2" style="width:200px;height:200px;
<div id="dv3" style="width:100px;height:100px;</div>
</div>
</div>
var dv1 = document.getElementById('dv1')
var dv2 = document.getElementById('dv2')
var dv3 = document.getElementById('dv3')
dv1.onclick = function (){
• console.log(this.id)
}
dv2.onclick = function (){
• console.log(this.id)
}
dv3.onclick = function (){
• console.log(this.id)
}
执行上面代码
2、捕获型
网景提出另一种事件流名为事件捕获。事件从最不精确的对象(document 对象)开始触发,然后到最精确(也可以在窗口级别捕获事件,不过必须由开发人员特别指定),与事件冒泡相反,事件会从最外层开始发生,直到最具体的元素。同样形象的比喻一下可以想象成警察逮捕屋子内的小偷,就要从外面一层层的进入到房子内。
dv1.addEventListener('click',f1,true)
dv2.addEventListener('click',f1,true)
dv3.addEventListener('click',f1,true)
function f1(){
console.log(this.id)
}
执行上面代码对比
3、混合型
w3c的dom事件支持捕获型和冒泡型两种事件流,捕获型事件流先发生,然后才发生冒泡型事件流
阻止事件冒泡
① e.stopPropagation()
②window.event.cancelBubble = true (谷歌,IE8兼容,火狐不支持)
③合并取消:return false
在javascript的return false只会阻止默认行为,而是用
2、事件机制
javascript从诞生之日起就是一门 单线程的 非阻塞的 脚本语言,单线程意味着,javascript代码在执行的任何时候,都只有一个主线程来处理所有的任务,非阻塞靠的就是 event loop(事件循环)
event loop它最主要是分三部分:主线程、宏队列(macrotask)、微队列(microtask)
event loop事件循环
js的任务队列分为同步任务和异步任务,所有的同步任务都是在主线程里执行的,异步任务可能会在macrotask或者microtask里面
js引擎遇到一个异步事件后并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。当一个异步事件返回结果后,js会将这个事件加入与当前执行栈不同的另一个队列,我们称之为事件队列。被放入事件队列不会立刻执行其回调,而是等待当前执行栈中的所有任务都执行完毕, 主线程处于闲置状态时,主线程会去查找事件队列是否有任务。如果有,那么主线程会从中取出排在第一位的事件,并把这个事件对应的回调放入执行栈中,然后执行其中的同步代码...,如此反复,这样就形成了一个无限的循环。这就是这个过程被称为“事件循环(Event Loop)”的原因。
当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件。同一次事件循环中,微任务永远在宏任务之前执行。
执行顺序
1、先执行主线程
2、遇到宏队列(macrotask)放到宏队列(macrotask)
3、遇到微队列(microtask)放到微队列(microtask)
4、主线程执行完毕
5、执行微队列(microtask),微队列(microtask)执行完毕
6、执行一次宏队列(macrotask)中的一个任务,执行完毕
7、执行微队列(microtask),执行完毕
8、依次循环。。。
常见的微任务有:process.nextTick、Promise。then和 MutationObserver(监听DOM变化的事件) 常见的宏任务有:setTimeout、setInterval、setImmediate、 script标签中包含整体的代码块、 I/O操作、 UI渲染等。
测试题目
console.log(1)
process.nextTick(() => {
console.log(8)
setTimeout(() => {
console.log(9)
})
})
setTimeout(() => {
console.log(2)
new Promise(() => {
console.log(11)
})
})
特殊说明: new Promise()属于主线程任务
let promise = new Promise((resolve,reject) => {
setTimeout(() => {
console.log(10)
})
resolve() // 这个console也属于主线程任务
console.log(4)
})
fn()
console.log(3)
promise.then(() => {
console.log(12)
})
function fn(){
console.log(6)
}
执行顺序
1、4、6、3、8、12、2、11、10、9。
分析:
1:先执行 console.log(1)
2:process.nextTick放入微任务队列、setTimeout放入宏任务队列
3: new Promise() console.log(4)执行、setTimeout放入宏队 列
4、fn()执行 console.log(6)执行
5、console.log(3)执行
6、promise.then放入微队列
此时主线程执行结束,打印 1、4、6、3
开始进行微队列。队列先进先出所以process.nextTick先打印8,又发现setTimeout放入宏队列
promise.then;打印12.微队列结束。
开始进行宏队列。setTimeout,先打印2;发现有new Promise,立即执行打印11、在进行第二个setTimeout打印10、进行最后一个setTimeout打印9。
此时既无微队列又无宏队列。结束
请写出下面代码的运行结果:// 今日头条面试题
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(function () {
console.log('settimeout')
})
async1()
new Promise(function (resolve) {
console.log('promise1')
resolve()
}).then(function () {
console.log('promise2')
})
console.log('script end')
答案:
script start
async1 start
async2
script end
async1 end
promise2
settimeout
注意: await async2()方法
await Promise问题
await 后面可以跟任何的JS 表达式。虽然说 await 可以等很多类型的东西,但是它最主要的意图是用来等待 Promise 对象的状态被 resolved。
如果 await 的是 promise对象,await 会暂停 async 函数内后面的代码,先执行 async 函数外的同步代码(注意,promise 内的同步代码会先执行),等着 Promise 对象 fulfilled,然后把 resolve 的参数作为 await 表达式的运算结果返回后,再继续执行 async 函数内后面的代码 如果 await 的不是一个 promise ,而是一个表达式。await 会暂停 async 函数内后面的代码执行,先执行 async 函数外的同步代码(注意,此时会先执行完 await 后面的表达式后再执行 async 函数外的同步代码)
这个题目因为async2()并不是一个 promise,所以会先执行console.log('async1 end'),再执行async2()