JavaScript Event Loop
今天要分享的是javascript事件的运行机制,我们知道js是单线程的,但是为什么会是单线程?它又是怎么样运行的?
带梯子的直接传送help i'm stuck in an event-loop
这小哥讲得透彻 😂😂😂
想直接看结果可以翻到底部
历史原因
很早以前网上冲浪越来越流行时,对于开发客户端脚本的需求也逐渐增大。这时网页已经不断地变得更大和更复杂。而更加加剧用户痛苦的是,仅仅为了简单的表单有效性验证,就要与服务器进行多次地往返交互。设想一下,用户填完一个表单,点击提交按钮,等待了 30 秒的处理后,看到的却是一条告诉你忘记填写一个必要的字段。
急需开发一种客户端脚本语言来解决简单的处理问题。在客户端处理一部分事情,优化体验,同时也减轻了服务端的压力,当初的考虑只是一个脚本类型的辅助操作浏览器语言。
但是JavaScript是单线程呢?
不妨反过来想,如果js是多线程会怎么样?多个线程对同一个dom操作,这该怎么办?频繁的操作又该怎么办?浏览器的处理性能问题。所以JavaScript开发出来的原因就决定了是什么。
事件调用机制 call stack
接下来就是我们的重头戏js调用机制了,由于是单线程语言,这就意味着它只有一个单一的调用堆栈,也就是每次只能做一件事。
就拿比较流行的V8引擎来说,组成由:
- 内存堆(memory heap): 分配内存的地方(可执行代码)
- 调用栈(call stack): 程序运行时候函数的调用过程
JS运行的时候,会有栈内存(stack)和堆内存(heap),当我们用new实例化一个类的时候,这个new出来的对象就保存在heap里面,而这个对象的引用则存储在stack里。程序通过stack里的引用找到这个对象。例如var a = [1,2,3];,a是存储在stack里的引用,heap里存储着内容为[1,2,3]的Array对象。
在这里我们引出调用栈的概念
也就是你所写的JS代码是如何执行的,不多逼逼,看代码
function a() {
console.log('a')
//console.trace()
}
function b() {
a()
}
function c() {
b()
}
c()
这段简单的代码,相信都能看出来调用都顺序
- 在全局调用了 c()
- c 调用了 b
- b 调用了 a
-
a 调用了console
换成在调用栈就是:如图 ⬇️
-
global => c => b => a => console
依次进栈
运行完毕后出栈 -
console => a => b => c => global
依次出栈
:可以用console.trace() 在控制台看到调用栈
嗯哼,很简单吧
how about
console.log('start')
setTimeout(function(){
console.log('setTimeout')
}, 100)
sleep(100000000)
console.log('end')
这就要说到js异步处理了,为啥要用异步?同步(按照代码执行顺序)执行,setTimeout 100000000,空闲的cpu就要一直等待100000000ms,才继续执行任务,巨大的资源浪费明显是不合理的。so
在这里我就直接给出答案了,反正你们打开控制台也能直接看到了
//start
//end
//setTimeout
那么
console.log('start')
setTimeout(function(){
console.log('setTimeout 100')
}, 100)
setTimeout(function(){
console.log('setTimeout 0')
}, 0)
console.log('end')
是0先输出呢还是100呢?
这样?
console.log('start')
setTimeout(function(){
console.log('setTimeout 1')
setTimeout(function(){
console.log('setTimeout 3')
}, 0)
}, 0)
setTimeout(function(){
console.log('setTimeout 2')
}, 1000)
console.log('end')
异步在栈里面是如何调用的?
其实一个图就能看懂了
- V8 遇到异步函数,函数进栈
- call back 交给WebAPIs
- 函数出栈
- 继续执行代码
... - WebAPI判定可以执行了
- call back 进入回调队列
- 代码执行完毕,栈清空
- 队列依次进栈