前言
JavaScript是一种广泛应用于网页开发和移动应用开发的编程语言,而event-loop是JavaScript中一个重要的概念。在本文中,我们将深入探讨JavaScript中event-loop的知识点,包括其定义、作用和实际应用。通过本文的阐述,读者将能够更好地理解JavaScript中event-loop的工作原理,从而更好地应用它来解决实际的开发问题。
正文
进程和线程
我们需要知道JS是一个单线程语言,所以先来解释一下什么是进程和线程。
进程: CPU运行指令和保存上下文所需时间。进程是计算机中正在运行的程序的实例。每个进程都有自己的内存空间、数据、代码和系统资源。进程之间是相互独立的,它们不能直接访问其他进程的数据。每个进程都有自己的地址空间,因此进程之间的通信需要通过特定的机制,比如管道、共享内存或消息队列。举个例子,一般来说我们手机开一个软件就像是一个进程,在电脑浏览器里开一个web就像是开一个进程。
线程: 进程中的更小的单位,描述了一段指令执行所需的时间线程是进程中的执行单元,一个进程可以包含多个线程。不同于进程,线程共享同一进程的地址空间和系统资源,因此线程之间可以直接访问同一进程的数据。线程之间的通信更加方便,可以直接通过共享内存等方式进行数据交换。一样的例子开一个web页面,需要多个线程配合才能完成页面的展示1. 渲染线程(GPU)2. http请求线程 3. js引擎线程等等
但是我们要知道渲染线程(GPU)和 js引擎线程 是互斥的,为什么呢?因为两个线程都能操作一个数据结构,这样的话如果同时操作,js在通过代码改变元素中的样式的时候,浏览器需要重新渲染,所以为了避免数据竞争和不一致的渲染结构,两者是不会同时进行的。
同步和异步
我们都知道js里面是分同步和异步代码的,要是v8在执行代码时,碰到了异步代码它就会把异步代码放到一边,先执行后续代码而不会等待这个操作完成,等同步代码执行完成再去执行那段代码,这也就构成了js的单线程。举个例子
js
let a = 1
console.log(a);
setTimeout(() => {
console.log(a);
}, 1000);
a = 2
在这段代码里,我们先执行前两行,然后碰到setTimeout函数,设置一个定时器,等待1秒后执行回调函数;然后执行第六行,等第六行执行完成后,等待一秒,定时器触发,执行回调函数。 所以输出结果为1 2,
再将这个例子改一下
js
let a = 1
console.log(a);
setTimeout(() => {
console.log(a);
}, 1000);
for (let i = 0;i < 10000; i++){ // 假设执行要1s
}
如果这个for循环执行也要一秒,那么你觉得执行顺序是怎么样的呢,其实还是一样的,因为for循环还是同步函数,它执行的快慢并不是确定的,还依赖于电脑的性能,比如你拿一台00年的电脑,搞一个10000次的循环,但是拿一台几万的电脑,两者运行的时间可能会差好几秒。
那js为什么不设计成多线程语言呢,因为js当年设计的时候就是为了节省这门语言对运行内存的开销,所以毫无疑问这门语言就是会比多线程语言运行慢,它的优点也显而易见:1. 节省内存 2. 单线程没有锁的概念,节省上下文切换时间。
然后给你一段代码
js
console.log('start');
setTimeout(() => {
console.log('setTiomeout');
setTimeout(() => {
console.log('inner');
})
}, 1000);
new Promise((resolve,reject) => {
console.log('Promise');
resolve()
})
.then(() => {
console.log('then1');
})
.then(() => {
console.log('then2');
})
我们知道了同步和异步的概念,但是看到这段代码还是不懂它的执行顺序啊,那我们接下来看。 异步其实还分成两种:
-
宏任务 (macrotask):
script标签
setTimeout定时器
setInterval
setImmediate
I/O
UI-rendering页面渲染
-
微任务 (microtask):
promise.then()
MutationObserver
Process.nextTick()
这两种都是定死的,宏任务有一个宏任务队列,微任务有一个微任务队列。v8执行代码就是先执行同步代码,同步代码执行完了就是执行异步代码。就像最开的代码,先执行同步代码,同步代码执行完了就执行异步代码,异步代码就是那个定时器代码,但是定时器里面是不是也可以写个几十行代码,然后这几十行代码里还可以放同步和异步代码,然后继续循环,是不是就可以一直循环下去,这就叫事件循环机制event-loop。
enent-loop
我们先来讲讲enent-loop的循环机制:
- 执行同步代码 (这属于宏任务)
- 当执行栈为空,查询是否有异步代码需要执行
- 如果有就执行微任务
- 如果有需要,会渲染页面
- 执行宏任务 (这也叫下一轮的event-loop的开启)
好了,有个这个机制,我们再看之前那段代码
js
console.log('start');
setTimeout(() => {
console.log('setTiomeout');
setTimeout(() => {
console.log('inner');
})
}, 1000);
new Promise((resolve,reject) => {
console.log('Promise');
resolve()
})
.then(() => {
console.log('then1');
})
.then(() => {
console.log('then2');
})
一整个代码流程就是这样了,文字描述一下
- 执行
console.log('start')
,输出start
。 - 调用
setTimeout
,设置一个定时器,等待1秒后执行回调函数。 - 执行
new Promise
,并立即执行其中的代码,输出Promise
。 - 执行
resolve()
,但由于setTimeout
中的回调函数未指定时间,因此立即执行下一个then
。 - 执行
then(() => {console.log('then1')})
,输出then1
。 - 执行
then(() => {console.log('then2')})
,输出then2
。 - 等待1秒后,
setTimeout
的回调函数执行,输出setTimeout
。 - 在
setTimeout
的回调函数中再次调用setTimeout
,但未指定时间,因此立即执行下一个then
。 - 执行第二个
then(() => {console.log('inner')})
,输出inner
。
ok,最后看一道题巩固一下
js
function A(){
return new Promise((resolve,reject) => {
setTimeout(() => {
console.log('异步A完成');
resolve()
}, 1000);
})
}
function B(){
setTimeout(() => {
console.log('异步B完成');
},500)
}
function C(){
setTimeout(() => {
console.log('异步C完成');
},100)
}
A()
.then(() => {
B()
})
.then(() => {
C()
})
这段代码怎么一回事,它输出的是异步A完成,异步C完成,异步B完成,为什么呢?因为.thenC那部分会跟着前面那个.thenB执行,前面那个开始执行这个就立马跟着开始执行,但是前面那个里面的内容执行需要时间,C这个执行更快所以后面这个异步C完成先输出,那么我们要是想让它顺序输出ABC应该怎么办?
js
function A(){
return new Promise((resolve,reject) => {
setTimeout(() => {
console.log('异步A完成');
resolve()
}, 1000);
})
}
function B(){
return new Promise((resolve,reject) => {
setTimeout(() => {
console.log('异步B完成');
resolve()
},500);
})
}
function C(){
setTimeout(() => {
console.log('异步C完成');
},100)
}
A()
.then(() => {
return B()
})
.then(() => {
C()
})
这样改就行了,因为.then虽然默认会返回promise对象,但是当.then的回调有人为返回promise对象时,.then默认的promise会失效。
在第一个例子中,函数B并没有返回一个Promise对象,所以在调用then
方法时,会直接执行函数B中的定时器,而不会等待函数B的定时器完成后再执行下一个then
。因此,函数C中的定时器不会等待函数B的定时器执行完毕,而是会立即执行。
在第二个例子中,函数B返回了一个Promise对象,因此在调用then
方法时,会等待函数B中的定时器完成后再执行下一个then
。
总结
本文主要介绍了事件循环(Event Loop)的相关知识,同时还涉及了异步和同步、微任务和宏任务、线程和进程等内容。以下是各部分的总结:
- 线程和进程:线程是操作系统中独立调度的基本单位,进程是资源分配的基本单位。JavaScript是单线程语言,但是可以通过Web Workers实现多线程操作。多线程操作可以提高程序的并发处理能力,但也会带来一些问题,如线程同步、死锁等。
- 异步和同步:在JavaScript中,异步操作不会阻塞程序的执行,而同步操作会阻塞程序的执行。异步操作通常使用回调函数、Promise和async/await等方式实现。
- 微任务和宏任务:微任务和宏任务都是异步操作,它们的执行顺序不同。微任务的执行顺序在宏任务之前,包括Promise的then/catch/finally、MutationObserver等;宏任务包括setTimeout、setInterval、I/O等。
- 事件循环(Event Loop):事件循环是JavaScript用于处理异步操作的机制。它负责监视调用栈和消息队列,确保执行顺序的稳定。事件循环的执行顺序为:首先执行同步任务,然后执行微任务,最后执行宏任务。
总之,了解事件循环、异步和同步、微任务和宏任务、线程和进程等概念,有助于我们更好地理解JavaScript的运行机制,以及如何编写高效、稳定的JavaScript程序。
觉得有帮助的大佬们点点赞吧!