JS单线程的理解

一、首先需要区分几个概念:

1. 进程和线程的概念:

  进程:指在系统中运行的一个应用程序,目的就是担当分配系统资源(CPU时间、内存等)的基本单位

  线程:系统分配处理器时间资源的基本单元,建立在进程的基础上,一个进程至少要有一个线程。

2.堆,栈,队列
    堆(HEAP):存放对象。
                     javascript所有皆为对象,如函数是“可调用对象”,在函数被调用之前,javascript引擎会对函数进行编译,完成编译后,函数会被放入堆中,分配内存空间,等待执行或调用。

    栈(STACK):执行栈。
                         如调用函数,响应用户操作事件。
                         当调用函数时,javascript引擎会创建一个执行栈,包含了被调用函数的参数和局部变量。                   

function A(){
B();
}

      此时,会把B函数推入执行栈,同时包含B函数的参数和局部变量。当B函数执行完毕后,B函数出栈,继承执行A函数,A函数执行完毕,出栈,整个执行栈就空了。然后主线程会读取任务队列中的其他任务。

   队列:待执行任务队列
              单线程意味着,所有任务都要排队,前一个任务执行完毕,才会执行后一个任务。
              任务可以分为两种,同步任务和异步任务。
              同步任务是指主线程上排队执行的任务,只有前一个任务执行完毕,后一个任务才会执行。
              异步任务是指不进入主线程,而进入任务队列的任务(如回调函数,定时器,事件响应等),只有当主线程上的所有同步任务执行完毕后,主线程才会读取任务队列,开始执行异步任务。

    定时器:
              定时器是异步任务,主线程会在所有同步任务执行完毕后,计算定时器的执行时间,再将事件推入执行栈。由此可知,定时器并不是完全准时执行的。
              还需要注意的是,定时器在异步任务队列中是按时间长短排列的,时间越短,越早执行。

function foo() {
setTimeout(function() {
console.log(1);
}, 2000)
console.log(2);
}

function bar() {
setTimeout(function() {
console.log(3);
}, 1000);
console.log(4);
}

function baz() {
setTimeout(function() {
console.log(5);
}, 0)
console.log(6);
}
foo();
bar();
baz();
//结果: 2, 4, 6, 5, 3, 1;

 

3. 浏览器是多进程,多线程的,而JS是单线程的:

  打开一个浏览器Tab页签,会同时创建多个进程,主进程、网络进程、渲染进程、GPU进程...;

  而浏览器的内核就运行在渲染进程中,渲染进程会同时创建GUI渲染线程、JS引擎线程、事件触发线程、定时器触发线程、异步HTTP请求线程;

  其中JS引擎进程就是我们常说的JS是单线程的概念。

 

二、同步任务和异步任务

  同步任务:在主程序上按顺序执行的任务就叫做同步任务,只有上一个任务执行完毕后,才会执行下一个任务

  异步任务:未进入主程序,被放置在一个叫做任务队列中的程序中,常见的有回调函数中或者定时器中的任务。

  首先程序会去执行同步任务,同步任务全部执行完毕则去任务队列检查是否存在异步任务;

  注:如果执行中遇到定时器或者延时器,会启动定时线程进行计时监控,如果倒计时为0,则将回调函数中的任务塞

    入到异步队列当作宏任务执行,不是在一开始就塞入任务队列中

 

三、宏任务和微任务

  除了可以分为同步任务和异步任务外,为了更好的理解执行流程,还可以细化为宏任务和微任务;

  宏任务包含:主代码、setTimeout、setInterval、setImmediate、i/o操作(输入输出,比如读取文件操作、网络请求)、

           ui render(dom渲染,即更改代码重新渲染dom的过程)、异步ajax等

  微任务包含:Promise(then、catch、finally)、process.nextTick、Object.observe(⽤来实时监测js中对象的变化)、

        MutationObserver(监听DOM树的变化)

  注:new Promise(() => { ****})中的任务是宏任务、同步任务,只有.then\.finally\.catch中的任务才是微任务、异步任务

    引擎在执行程序的顺序是宏任务-->微任务-->GUI渲染任务-->宏任务....;

  启动执行程序,JS引擎会把整个JS当作宏任务进入主程序开始执行,宏任务执行完成后,会去查看是否存在微任务并执行完毕,然后继续执行宏任务,

  由于主程序不知道是否存在微任务,所以每次宏任务执行完毕后都会去检查是否存在微任务,这种周而复始的过程就叫做事件循环。

四、示例说明

console.log(1);
var timeout1 = setTimeout(() => {
  console.log(2);
  new Promise((resolve) => {
    console.log(3);
    resolve();
  }).then(() => {
    console.log(4);
  })
})

new Promise((resolve) => {
  console.log(5);
  resolve();
}).then(() => {
  console.log(6);
})

var timeout2 = setTimeout(() => {
  console.log(7);
  new Promise((resolve) => {
    console.log(8);
    resolve();
  }).then(() => {
    console.log(9);
  })
})
console.log(10);

打印结果为:1  5  10  6  2  3  4  7  8  9;

 

过程详解:

1. 首先顺序执行第1行打印结果为:1;

2. 执行2-10行为异步任务,启动定时进程进行倒计时,但是不会立即添加异步任务到任务队列中

3. 执行12-17行,按照上文备注promise()中的方法是宏任务所以会顺序执行,同时打印出5, 其中.then是异步任务所以会添加到任务队列的微任务中.

4. 执行19-27行与第二步类似,启动定时进程进行倒计时

5. 执行28行打印结果为:10

6. 宏任务全部执行完毕后就会去任务队列中检查是否存在微任务并执行,打印结果为:6

7. 倒计时16ms左右结束时,按照代码的先后顺序首先将timeout1中的异步任务添加到任务队列

8. 将timeout1中的任务添加到主程序当作一个宏任务开始执行所以打印结果是:2, 3;然后将.then中的任务添加到微任务中,

  每个宏任务后都会执行一个事件循环检查,然后开始执行微任务,最终打印结果为:2,3,4

9. timeout2会重复执行7、8两步,打印结果为7,8,9

posted @ 2023-07-20 11:30  enShine  阅读(264)  评论(0)    收藏  举报