js是如何单线程执行的

据大家所知,js都是单线程执行的。那么就会接触到线程与进程,同步执行与异步执行,以及js单线程执行原理概念。

一、进程与线程的概念

1.1 进程

进程是CPU进行资源分配的基本单位,浏览器使用的是多进程,一个标签对应一个进程,此进程负责管理各个标签的创建与销毁,前进后退等操作。

1.2 线程

线程是CPU调度最小单位,多个线程可以对应到单一进程,而且可以它们可以共享进程的内存大小。

二、js单线程是如何实现同步与异步

2.1 js为什么是单线程

若js是多线程的话,同时执行会有线程冲突。
例如第一线程在DOM的node节点A对他进行新增内容,第二个线程则对此A节点进行了删除操作,那么最终是以哪个进程为主?
所以js是采用单线程,一次仅仅会执行一件同步任务。但不代表js引擎中只有一个线程,只是代表只有一个线程(通常称为主线程)是用来执行js代码,其它线程则是处理后台配合。

2.2 js执行同步任务

2.2.1 js执行同步任务的原理

js是单线程也就是说同时只能执行一个任务,而其它剩下的任务需要在后面等待。当前面的任务执行完了,才执行后面的任务。

2.2.2 js执行同步任务与栈的关系

js在执行同步任务时,所选用的数据结构是栈,保持先进后出,有序的概念。
例如执行一个任务函数A会调用函数B,那么函数B的栈帧变成当前帧,而函数A的栈帧会变成调用帧。当函数B执行完才回调到函数A继续把A执行完。

2.3 js执行异步任务

2.3.1 js执行异步任务的原理

当JS碰到异步执行的任务时,会先把它放到任务队列中,暂不处理此异步任务,而是继续往下执行同步任务,这样不会进行使JS单线程任务阻塞。
当js单线程空闲下来以及任务队列中的异步任务通知可以执行了,才会把此异步任务从任务队列中取出来,放到线程上执行。

2.3.2 什么是任务队列

任务队列是用来存放js单线程所遇到的异步任务。任务队列有多个。

2.3.3 js执行异步任务与队列的关系

我们知道任务队列是以队列的数据结构,保持先进先出,有序的概念。
也就是说,当js单线程的执行栈空了,就会从任务队列按顺序取出来依次执行(要是任务队列都通知队列中异步任务都能执行的情况下)

2.3.4 任务队列是如何运用

进行回调函数的形式通知执行栈可以执行。
基本以下情况会通过Web APIs按照一定的规则,再对上面的任务进行划分宏事件放入宏队列中,微事件放如微队列中(此概念会在2.3.5进行叙述),例如:

  1. DOM Binding模块处理一些onclick的函数
  2. network模块处理ajax请求
  3. 使用计时器setTimeout
  4. 使用promise

为了能够实时检查到任务队列中是否能够回调用到主线程中运行,使用的是事件循环Event Loop。
就是创建一个类似while(true)的循环过程,循环过程成为Tick。JS的单线程不停的一遍一遍循环,查看任务队列中是否有异步任务要执行,如果有则取出该相关事件,并通过回调函数放入单线程的执行栈中。

2.3.5 任务队列的宏任务,微任务

2.3.5.1 什么是宏任务,微任务

  • 宏任务:js中全局的同步代码,setTimeout管理模块,AJAX请求管理模块,Dom事件管理模块等
  • 微任务:Promise管理模块,mutation管理模块

2.3.5.2 宏任务与微任务的执行原理

在同步任务都执行完,执行栈为空的基础下(同步任务相当于第一个宏任务执行完),每当宏任务执行完之后,会先查看是否微队列中有任务。有则全部执行完再执行第二个宏任务;没有则直接执行第二个宏任务。以此类推。

2.3.5.3 宏任务与微任务的执行代码理解以及动图

console.log('start')
setTimeout(function() {
    
    
  console.log('setTimeout')
}, 0)

Promise.resolve().then(function() {
    
    
  console.log('promise1')
}).then(function() {
    
    
  console.log('promise2')
})

console.log('end')

具体代码执行栈以及对应任务队列操作如下:
在这里插入图片描述

在上述代码以及动图中可以看出,从上往下顺序执行。
我们先分成同步任务与异步任务两部分:
一、同步执行
1.遇到console.log(‘start’)属于同步任务,直接执行。
2.遇到setTimeout代码块属于异步任务中的宏任务,把它放入任务队列中的宏队列macrotask挂起。
3.遇到Promise代码块属于异步任务中的微任务,把它放入任务队列中的微队列microtask中挂起。
4.遇到console.log(‘end’)属于同步任务,直接执行,至此同步任务已全部执行完成。

二、异步执行
1.同步任务完成之后,全局代码属于macrotask宏任务,此宏任务执行完之后就开始检查任务队列中的微队列是否有执行。
2.把微任务的Promise代码块回调,放入执行栈中执行,执行其中的console.log(‘promise1’)。
3.微任务的Promise代码块回调函数返回undefined。接着,primose的状态会从pending转换成fulfilled,继续放入microtask微队列之中。
4.进入event loop事件循环,此时执行.then()回调执行console.log(‘promise2’),至此异步任务的微任务队列完成。
5.再次进入event loop事件循环,就执行异步任务中的宏任务(此时微任务已没有需要执行,所以直接执行下一个宏任务)。执行setTimeout(),延时0之后,console.log(‘setTimeout’)。
6.至此任务队列全部执行完成,event loop还在循环等待新一轮需要处理的代码。

2.3.6 异步任务中的async-await

线程的执行顺序同时还会牵扯到async-await,promise,setTimeOut影响。现在主要重点关注async-await。

2.3.6.1 async-await是什么?

async是个函数且返回一个Promise对象,和await搭配使用。当在async函数中碰到await时,await就像是让出线程的操作,右边内容完成时让出线程,并把async剩下的代码挂起,等待剩下同步任务完成后才执行。

2.3.6.1 async-await,promise,setTimeOut的代码运行

	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')
	},0)
	async1();
	new Promise(function(resolve){
    
    
	  console.log('promise1')
	  resolve();
	}).then(function(){
    
    
	  console.log('promise2')
	})
	console.log('script end') 

运行后代码打印为
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout

可以看出function async2()返回是一个Promise,await async2()相当function async2()的,then()执行成功返回会的值。此时awiat会让出线程,跳出function async1()的异步执行,继续执行下面的同步任务,等同步任务全部执行完毕,再执行function async1()剩下的部分。

猜你喜欢

转载自blog.csdn.net/Ak47a7/article/details/130033501
今日推荐