彻底理解js是单线程的

JS执行是单线程

单线程是指Js引擎执行Js时只分了一个线程给他执行,也就是执行js时是单线程的。

那么问题来了,什么是线程?进程又是什么?

在分析浏览器的渲染过程之前,我们先了解一下什么是进程和线程:

(1)什么是进程?

进程是CPU进行资源分配的基本单位

(2)什么是线程?

线程是CPU调度的最小单位,是建立在进程的基础上运行的单位,共享进程的内存空间。

多进程
1、浏览器是多进程

2、不同类型的标签页都会开启一个新的进程

3、相同类型的标签页是会合并到一个进程

1、浏览器进程

(1)负责管理各个标签页的创建和销毁

(2)负责浏览器的页面显示和功能(前进,后退,收藏等)

(3)负责资源的管理与下载

2、第三方插件进程

(1)负责每个第三方插件的使用,每个第三方插件使用时候都会创建一个对应的进程

3、GPU进程

(1)负责3D绘制和硬件加速

4、浏览器渲染进程(咱们这回主要分析的)

1、浏览器内核,主要负责HTML,CSS,JS等文件的解析和执行

什么是浏览器内核?

浏览器内核就是浏览器渲染进程,从接收下载文件后再到呈现整个页面的过程,由浏览器渲染进程负责,主要流程如下:

1、解析HTML文件和CSS文件,加载图片等资源文件,渲染成用户看到的页面
2、执行解析js文件脚本代码

这里主要讲浏览器页面渲染过程,在该过程中浏览器渲染进程会开启多个线程协作完成,主要的线程以及作用如下:

1、GUI渲染线程
2、JS引擎线程
3、事件触发线程
4、定时器出发线程
5、异步HTTP请求线程

什么是JS引擎?

1、JS内核,也称JS引擎(例如V8引擎),负责处理执行javascript脚本程序,
2、由于js是单线程(一个Tab页内中无论什么时候都只有一个JS线程在运行JS程序),依靠任务队列来进行js代码的执行,所以js引擎会一直等待着任务队列中任务的到来,然后加以处理。

注意,JavaScript 只在一个线程上运行,不代表 JavaScript 引擎只有一个线程。事实上,JavaScript 引擎有多个线程,单个脚本只能在一个线程上运行(称为主线程),其他线程都是在后台配合

为了利用多核 CPU 的计算能力,HTML5 提出 Web Worker 标准,允许 JavaScript 脚本创建多个线程,但是子线程完全受主线程控制,且不得操作 DOM。所以,这个新标准并没有改变 JavaScript 单线程的本质。

V8引擎的内部结构

1、V8是一个非常复杂的项目,使用cloc统计可知,它竟然有超过100万行C++代码。
2、V8由许多子模块构成,其中这4个模块是最重要的:

  1. Parser:负责将JavaScript源码转换为Abstract Syntax Tree (AST)
  2. Ignition:interpreter,即解释器,负责将AST转换为Bytecode,解释执行Bytecode;同时收集3. TurboFan优化编译所需的信息,比如函数参数的类型;
  3. TurboFan:compiler,即编译器,利用Ignitio所收集的类型信息,将Bytecode转换为优化的汇编代码;
  4. Orinoco:garbage collector,垃圾回收模块,负责将程序不再需要的内存空间回收;

再次强调:单线程是指Js引擎执行Js时只分了一个线程给他执行,也就是执行js时是单线程的。

既然JS是单线程的,那怎么实现异步的呢?

单线程意味着什么:JavaScript 同时只能执行一个任务,其他任务都必须在后面排队等待。也就是说代码只能同步执行,必须执行上一行才能执行下一行。

console.log('1')
setTimeout(() => {
    console.log('2')
}, 0);
console.log('3')

然而并不是,还有异步!!!

程序里面所有的任务,可以分成两类:同步任务(synchronous)和异步任务(asynchronous)。

同步任务是那些没有被引擎挂起、在主线程上排队执行的任务。只有前一个任务执行完毕,才能执行后一个任务。

异步任务是那些被引擎放在一边,不进入主线程、而进入任务队列的任务。只有引擎认为某个异步任务可以执行了(比如 Ajax 操作从服务器得到了结果),该任务(采用回调函数的形式)才会进入主线程执行。排在异步任务后面的代码,不用等待异步任务结束会马上运行,也就是说,异步任务不具有“堵塞”效应。

举例来说,Ajax 操作可以当作同步任务处理,也可以当作异步任务处理,由开发者决定。如果是同步任务,主线程就等着 Ajax 操作返回结果,再往下执行;如果是异步任务,主线程在发出 Ajax 请求以后,就直接往下执行,等到 Ajax 操作有了结果,主线程再执行对应的回调函数。

那怎么实现异步的呢?

任务队列和事件循环

JavaScript 运行时,除了一个正在运行的主线程,引擎还提供一个任务队列(task queue),里面是各种需要当前程序处理的异步任务。(实际上,根据异步任务的类型,存在多个任务队列。为了方便理解,这里假设只存在一个队列。)

首先,主线程会去执行所有的同步任务。等到同步任务全部执行完,就会去看任务队列里面的异步任务。如果满足条件,那么异步任务就重新进入主线程开始执行,这时它就变成同步任务了。等到执行完,下一个异步任务再进入主线程开始执行。一旦任务队列清空,程序就结束执行。

异步任务的写法通常是回调函数。一旦异步任务重新进入主线程,就会执行对应的回调函数。如果一个异步任务没有回调函数,就不会进入任务队列,也就是说,不会重新进入主线程,因为没有用回调函数指定下一步的操作。

JavaScript 引擎怎么知道异步任务有没有结果,能不能进入主线程呢?答案就是引擎在不停地检查,一遍又一遍,只要同步任务执行完了,引擎就会去检查那些挂起来的异步任务,是不是可以进入主线程了。这种循环检查的机制,就叫做事件循环(Event Loop)。维基百科的定义是:“事件循环是一个程序结构,用于等待和发送消息和事件(a programming construct that waits for and dispatches events or messages in a program)”。

代码是如何执行的?

宏任务(macro-task)、微任务(micro-task)

除了广义的同步任务和异步任务,JavaScript 单线程中的任务可以细分为宏任务和微任务。

macro-task包括:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。
micro-task包括:process.nextTick, Promises, Object.observe, MutationObserver。

有了宏任务和微任务的概念后,那 JS 的执行顺序是怎样的?是宏任务先还是微任务先?

第一次事件循环中,JavaScript 引擎会把整个 script 代码当成一个宏任务执行,执行完成之后,再检测本次循环中是否寻在微任务,存在的话就依次从微任务的任务队列中读取执行完所有的微任务,再读取宏任务的任务队列中的任务执行,再执行所有的微任务,如此循环。JS 的执行顺序就是每次事件循环中的宏任务-微任务。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章