进程和线程

进程:是计算机的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的「基本单位」(正在运行的程序就叫进程)
多进程:顾名思义就是启动多个进程,多个进程可以一块来执行多个任务。

线程:进程内一个相对独立的,可调度的执行单元;于同属的一个进程共享着进程中的资源(同一时间只能做一件事情)。
多线程:启动一个进程,在一个进程内部启动多个线程,这样多个线程也可以一块执行多个任务(也是通过调度来做的,如同多线程能做多任务一样,多线程也能做多任务)。

以上这些概念可能不是很好理解,我们可以把进程和线程比喻成我们生活中的工厂和工人:
进程好比一个工厂,线程好比工厂里的工人;
工厂的资源:系统分配的内存(独立的一块内存);
工厂之间相互独立:进程之间相互独立;
多个工人协作完成任务:多个线程在进程中协作完成任务;
工厂内有一个或多个工人:进程内由一个或多个线程组成;

浏览器的多线程

浏览器是多进程的,我们常说的「浏览器渲染引擎」就是进程中的一个。
浏览器渲染引擎中常驻的线程有:

  1. JS引擎线程:该引擎主要是解释和执行JS,另外它是单线程,这主意是为了避免两个JS文件同时操作同一个DOM引起混乱
  2. GUI线程:绘制用户界面,它和JS引擎是互斥的
  3. HTTP网络请求线程
  4. 定时器线程
  5. 浏览器事件处理线程

    HTTP线程、定时器线程和浏览器事件处理线程统称为Web APIs,主要是一些异步操作。

JS 的运行原理

单线程由于是一个“工人”在工作,数据处理能力自然就会慢很多。
如果遇到数据量大的时候怎么办呢?
目前的解决方案有:1、SSR服务端渲染 2、webworkerJS单线程申请子线程,子线程由浏览器进程开启

另外由于JS引擎是单线程的,如果遇到等待的场景(例如:网络请求,如果一直不返回数据,那么页面就会一直卡着不动)就需要用到异步的方案。
异步可以理解为:同一个时间处理两个事情。
**JS**引擎通过「事件驱动」的方式模拟来实现异步(事件驱动是基于浏览器提供的环境,不同的环境基于事件驱动的模型可能不太一样)
另外,所有的异步代码全部是以回调函数的方式出现的,并不是所有的回调函数都是异步代码。

  1. // setTimeout 就是异步函数
  2. setTimeout(function(){
  3. // do...
  4. }, 1000)
  5. // Array 的 sort 方法是同步代码
  6. arr.sort(function(a, b){
  7. return a - b;
  8. })

事件循环

JS引擎并不是单独运行的,它是在一个宿主环境中运行的,对于大多数开发人员来说,宿主环境就是典型的web浏览器或Node.js
所有环境中的共同点是一个称为事件循环的内置机制,它处理程序的多个块在一段时间内通过调用JS的引擎执行。
image.png :::info 先来了解几个概念:
Call Srack:调用堆栈,也可以理解为主线程
Web APIs:异步任务,也可以理解为子线程或者待回调的任务
Callback Queue:任务队列,子线程中的程序达到一定条件后会添加任务队列中
Event Loop:事件循环,先看主线程任务有没有执行完成,如果执行完成再看任务队列,如果任务队列中有任务,那么就会将任务推入到主线程中执行 :::

举例 🌰:

  1. console.log("Hi");
  2. setTimeout(function () {
  3. console.log("cb1");
  4. }, 5000);
  5. console.log("Bye");

image.png
初始化状态都为空,浏览器控制台是空的的,调用堆栈也是空的
image.png
console.log('Hi')添加到调用堆栈中
image.png
执行console.log('Hi')
image.png
console.log('Hi')调用堆栈中移除
image.png
setTimeout(function cb1() { ... })添加到调用堆栈
image.png
setTimeout(function cb1() { ... })执行,浏览器创建一个计时器计时,这个作为**Web api**的一部分
image.png
setTimeout(function cb1() { ... })本身执行完成,并从调用堆栈中删除
image.png
console.log('Bye')添加到调用堆栈
image.png
执行console.log('Bye')
image.png
console.log('Bye')从调用调用堆栈移除
image.png
至少在 5 秒之后,计时器完成并将**cb1**回调推到回调队列。
image.png
事件循环从回调队列中获取**cb1**并将其推入调用堆栈。
image.png
执行**cb1**并将**console.log('cb1')**添加到调用堆栈。
image.png
执行console.log('cb1')
image.png
console.log('cb1')从调用堆栈中移除
image.png
cb1从调用堆栈中移除

ES6指定了事件循环应该如何工作,这意味着在技术上它属于JS引擎的职责范围,不再仅仅扮演宿主环境的角色。这种变化的一个主要原因是ES6中引入了Promises,因为ES6需要对事件循环队列上的调度操作进行直接、细度的控制。

setTimeout 是怎么工作的?

setTimeout不会自动将回调放到事件循环队列中。它设置了一个计时器。当计时器过期时,环境将回调放到事件循环中,以便将来某个标记将接收并执行它。

  1. setTimeout(myCallback, 1000);

这并不意味着**myCallback**将在 1000 毫秒后就立马执行,而是在 1000 毫秒后,**myCallback**被添加到队列中。但是,如果队列有其他事件在前面添加回调则必须等待前面的执行完后再执行**myCallback**(所以可能会存在一点点误差)。

调用setTimeout的时候 0 毫秒作为第二个参数只是推迟回调将它放到回调队列中,直到调用堆栈是空的。

  1. console.log('Hi');
  2. setTimeout(function() {
  3. console.log('callback');
  4. }, 0);
  5. console.log('Bye');
  6. // Hi
  7. // Bye
  8. // callback

感谢