JavaScript Event Loop

369 阅读4分钟

JavaScript 是什么?

是一个单线程、非阻塞、异步、解释性脚本语言

web APIs、DOM、AJAX、setTimeout之类的东西是异步的

在JavaScript 处理 异步时,会经过一下两个概念

事件循环(Event Loop) 回调队列 (task queue)

JavaScript 调用栈

JavaScript 是一个单线程的一个运行环境,它有且只有一个调用栈,它每次只能做一件事

这是单线程的意思,程序每次只可以运行一段代码

// 我们先来理解一下 调用栈 就是 JavaScript 是如何执行代码的
function multiply(a,b){
	return a * b
}
function square(n){
	return multiply(n)
}
function printSquare(n){
	let squared = square(n)
    console.log(squared)
}

printSquare(4)

在 JavaScript 的执行栈是后进先出.

先看一开始的栈

  • 4. multiply(n,n)
    1. square(n)
    1. printSquare(4)
    1. main()

mulyiply 返回值之后 执行完毕就被弹出了。(只剩1 2 3)接着square函数执行完毕返回值也被弹出了(只能1 2),squared拿到了square的返回值,到console.log() 压栈进去 把squared的值输出,然后被弹出栈。printSquare 执行完毕 被弹出栈。main 弹出

main() 根 c语言的主函数main一样的,是一个入口。在JS里你可以理解当前这个整体js文件

我们可以在浏览器的控制台看看调用栈的顺序,

function foo(){
	throw new Error('Oops')
}
function bar(){
	foo()
}
function baz(){
	bar()
}
baz();

把上面代码放在浏览器控制台的话会报错的

VM65:2 Uncaught Error: Oops
    at foo (<anonymous>:2:8)
    at bar (<anonymous>:5:2)
    at baz (<anonymous>:8:2)
    at <anonymous>:10:1
    
// 它把真个栈树都打印出来,从 foo - bar - baz - 匿名函数(main)

栈也有大小的,你可以写一个调用自身的函数,一直这样调用直指到爆栈

到目前为止,我们写的代码来说并不会让人感到卡顿或者感觉运行很慢的感觉 比如遍历1-10亿很慢、网络请求、下载图片会很慢,所有很慢的东西在栈里都叫阻塞

例如 伪码 AJAX 代码

let foo = $.getSync('//foo.html')
let bar = $.getSync('//bar.html')
let baz = $.getSync('//baz.html')

console.log(foo)
console.log(bar)
console.log(baz)

// 我这里就不一个个用栈把它们给列出来辣
// 整体的栈是:main() -> $.getSync('//foo.html') 依次往下运行()

现在有个问题就是,第一次$.getSync('//foo.html') 需要等它请求成功再请求bar.html以及baz.html 那由于JavaScript是单线程的每次只能运行一个。那我们在等它请求foo.html 时间 这段时间啥都干不了 这段时间 阻塞了!卡住了 如果在浏览器里,浏览器那段时间不能做其他的响应

在我们使用这些异步函数操作时,一般都会带上一个回调函数 例如 DOM click、 AJAX 请求、setTimeout 这些都是异步的,带上回调函数进行处理

JavaScript Event Loop

我们来看看这些异步跟上述的同步的执行有什么区别

console.log(1)

setTiemeout(function(){
	console.log(3)
},2000)

console.log(2)
// 1
// 2
// 3

我们知道输出的顺序是怎样的,但你知道浏览器是怎么执行的吗?

//首先
// Stack:										webapis	:		
// 		main()											setTimeout()
// 		console.log(1) 									时间到了 放到taskqueue
// 		setTimeout(function(){})(一看是异步操作,弹出 放到 webapis)	
// 		console.log(2)
// 	
// Event loop
// Task queue : 

首先我们用文字来看看是如何执行的。 首先 1. 执行 console.log(1) 2. 执行 setTimeout() 是一个异步函数 弹出 会放到 webapis 时间到了放到task queueu 3. 执行 console.log(2) 3. 弹出main() 现在栈是空的没任务了 4. 现在去 task queue 看看有没有要执行的任务(每次只取一个)有就压栈里面去 5. 执行从 task queue拿来的任务执行。 6. 只要栈空了就会取task queue 拿任务执行 会不断的轮询(这就是Event loop)

总结: 来大致的总结一下。 本文章的内容主要来自www.youtube.com/watch?v=8aG…

由于 JavaScript 是单线程的。每次执行js文件时,里面有同步任务和异步任务。代码是从上往下一步步执行的,遇到同步代码直接执行,遇到异步代码直接挂起,异步代码被挂起之后,有结果了就把回调函数放在 task queue里面,stack 里面的代码执行完毕之后取 task queue 不断轮询是否有代码执行(每次拿一个),有就拿来执行

  1. 所有同步代码在stack (主线程)里执行(从上往下,压栈 执行 弹出)
  2. 所有异步代码被挂起,到了相应的时间或者被触发把回调放在任务队列(task queue)
  3. 当主线程里的同步代码执行完毕之后,就会不断的取 task queue进行轮询是否有代码可执行

主线程不断重复上面的事做