Event Loop(事件循环)
javascript是一门单线程的语言,它的异步和多线程都是通过Event Loop实现的
console.log(1)
function func1() {console.log(3)
}
function func2() {console.log(2)func1()console.log(4)
}
func2()
- 调用栈(与栈内存不是一个东西)与控制台
1、读第一行代码,会把该代码放入调用栈中执行,在控制台中输出1,执行完毕后,从调用栈弹出
2、两个函数的声明不执行,调用func2时,这句代码放入调用栈;然后进入func2函数中,读取代码console.log(2)
,把这句放入调用栈,在控制台输出2,执行完又弹出栈,然后继续读;
3、读到func1函数,把这句代码放入调用栈,然后进入func1函数内部,读取console.log(3)
,放入栈中执行,控制台输出3,然后这句代码弹出栈,func1执行完毕,func1也弹出栈;
4、然后回到func2函数中读取console.log(4)
,放入栈中执行,控制台输出4,然后弹出该语句;func2执行完毕,然后弹出该语句,整个代码执行完毕
5、输出结果为1,2,3,4
先同步,后异步
宏任务与微任务
js异步任务分为两种:
- 宏任务:setTimeout(计时器)、setInterval()、Ajax、DOM事件
- 微任务:Promise
setTimeout(()=>{console.log('定时器执行')
},0);
for(var i = 0;i<10;i++){console.log('for循环')
}
运行结果是先打印10次for循环,再打印“计时器执行”,与代码读取的顺序相反,所以我们可以得出结论,异步任务在同步任务执行后再执行
微任务的优先级 > 宏任务的
var p = new Promise(resolve => {console.log(4)resolve(5)
})
// promise在创建时是一个同步的任务,也就是说上述代码中
// resolve函数的执行是同步的
// promise异步是当promise状态发生改变时是异步任务function func1 () {console.log(1)
}function func2() {setTimeout(() => {console.log(2)}, 0)func1()console.log(3)p.then(resolve => {// 这一段拿到点then的时候这一段代码是一个异步任务console.log(resolve)})
}
func2()
- 先执行第一段代码
var p = new Promise(resolve => {console.log(4)resolve(5)// 异步
})
读取第一句console.log(4)
,放入栈中,控制台输出4,然后语句弹出栈;读取下一句resolve(5)
,这个函数的调用,是promise状态改变的过程,所以是一个异步的任务,所以这一句不会先执行
- 执行第二段
function func1 () {console.log(1)
}function func2() {setTimeout(() => {console.log(2)}, 0)func1()console.log(3)p.then(resolve => {// 这一段拿到点then的时候这一段代码是一个异步任务console.log(resolve)})
}
func2()
1、中间的两个函数声明不执行,读func2函数调用,放入栈中,进入函数内部,因为有个计时器,所以这部分不会先执行,会放到一个宏任务队列中;
2、然后读取func1函数,这个语句放入栈中,然后进入func1函数,读到console.log(1)
,放入栈中,控制台输出1,然后语句弹出栈,而函数func1执行完毕后,也会弹出栈
3、读取console.log(3),放入栈,控制台输出3,然后该语句弹出;然后读到p.then这部分,由于前面我们调用resolve函数时,promise状态改变,变为异步任务,所以这段代码会被放入微任务队列,至此,代码中的同步任务被执行完毕,只剩下异步任务(宏、微)
4、因为优先级,所以微任务先放入栈中执行,也就是先执行p.then部分的resolve函数,函数由前面我们调用时resolve(5)
,传入了参数5,所以控制台输出了5,然后此代码弹出
5、然后宏任务(setTimeout)部分的代码放入栈中执行,控制台输出2,然后弹出该代码
6、综上所述:整个代码的执行结果是:4,1,3,5,2
<button id="button">按钮</button>
// 定义一个按钮
<script>var btn = document.getElementById('button')// 通过方法获取页面中button这个元素的idbtn.addEventListener('click', () => {// 对当前这个按钮的click事件进行监听Promise.resolve().then(() => console.log(1))// Promise.resolve()这是promise的一个方法,用来改变promise的状态,后面的点then就是异步的过程了console.log('listener 1')// 这一句是同步的任务})btn.addEventListener('click', () => {Promise.resolve().then(() => console.log(2))console.log('listener 2')})// 触发事件btn.click();
</script>
1、通过触发事件btn.click();
,这句代码放入栈中,监听功能开始执行,代码由上往下执行,所以第一部分的then中的内容因为是异步的,会被放到微任务队列,然后执行同步的console.log('listener 1')
,控制台输出listener 1,然后该语句弹出栈
2、执行第二部分的then后的内容,与第一部分一样,把异步任务放入微任务队列,执行同步任务console.log(‘listener 2’),放入栈中执行,控制台输出listener 2,弹出语句,然后btn.click();执行完毕,也弹出栈
3、当代码中同步任务都执行完毕后,只剩微任务队列中的两个任务,因为是队列,所以是先进先出的原则,所以第一部分的异步任务先放入栈中执行,控制台输出1,然后弹出栈;接着是第二部分的微任务,最后输出2,弹出栈,执行完毕
4、输出结果是:listener 1,listener 2,1,2
上面是用代码来触发事件的,然而我们在页面中定义了一个button按钮,通常是通过点击按钮来触发事件,这与用代码来触发有所不同
通过点击页面中按钮,我们可以观察到控制台输出的结果是:
listener 1,1,listener 2,2
我们先分别监听了两种事件,用户点击按钮这种方式,代码会按照监听顺序的先后分为两个任务,完成一个再监听下一个。也就是先执行第一部分任务的同步任务,异步任务,再执行第二部分