JavaScript的三座大山之单线程和异步

目录
  • 一、进程与线程
    • 1. 进程:
    • 2. 线程:
    • 3. 进程与线程的关系:
    • 4. 浏览器运行是单进程还是多进程?
    • 5. 如何查看浏览器是否是多进程运行的呢?
    • 6. 浏览器运行是单线程还是多线程?
  • 二、单线程
    • 1、什么是单线程
    • 2. JavaScript为什么是单线程
  • 三、同步与异步
    • 1、JS的 同步任务/异步任务
    • 2、 JavaScript为什么需要异步
    • 3、JavaScript怎么实现异步
      • 1)执行栈与任务队列
      • 2)宏任务(macro task)与微任务(micro task)
  • 总结

一、进程与线程

1. 进程:

程序的一次执行, 它占有一片独有的内存空间  ---- 可以通过windows任务管理器查看进程;

2. 线程:

是进程内的一个独立执行单元;是程序执行的一个完整流程;CPU的基本调度单位;

3. 进程与线程的关系:

* 一个进程中一般至少有一个运行的线程: 主线程 -- 进程启动后自动创建;

* 一个进程中也可以同时运行多个线程, 我们会说程序是多线程运行的;

* 一个进程内的数据可以供其中的多个线程直接共享;

* 多个进程之间的数据是不能直接共享的

4. 浏览器运行是单进程还是多进程?

  • 有的是单进程
  • firefox
  • 有的是多进程
  • chrome

5. 如何查看浏览器是否是多进程运行的呢?

任务管理器==>进程

6. 浏览器运行是单线程还是多线程?

都是多线程运行的

二、单线程

1、什么是单线程

JavaScript语言的一大特点就是单线程,也就是说同一时间只能做一件事。

//栗子 console.log(1)console.log(2)console.log(3)//输出顺序 1 2 3

2. JavaScript为什么是单线程

  • 首先是历史原因,在创建 javascript 这门语言时,多进程、多线程的架构并不流行,硬件支持并不好。
  • 其次是因为多线程的复杂性,多线程操作需要加锁,编码的复杂性会增高。
  • 最后与它的用途有关,作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM,如果同时操作 DOM ,在多线程不加锁的情况下,最终会导致 DOM 渲染的结果不可预期。

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

三、同步与异步

1、JS的 同步任务/异步任务

同步任务:

主线程上排队执行的任务,只有一个任务执行完毕,才能执行一个任务;

所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。

 异步任务:主线程外执行的任务;在主线程之外还存在一个“任务队列”(task queue),当异步任务执行完成后会以回调函数的方式放入任务队列中等待,等主线程空闲时,主线程就会去事件队列中取出等待的回调函数放入主线程中进行执行。这个过程反复执行就形成了js的事件循环机制(Event Loop)。

//栗子
// 同步
console.log(1)
// 异步
setTimeout(()=>{
    console.log(2)
},100)
// 同步
console.log(3)
//输出顺序 1 3 2

2、 JavaScript为什么需要异步

如果在JS代码执行过程中,某段代码执行过久,后面的代码迟迟不能执行,产生阻塞(即卡死),会影响用户体验。

3、JavaScript怎么实现异步

1)执行栈与任务队列

其实上面我们已经提到了,JS实现异步时通过 事件循环

我们先理解几个概念:

  • JS任务 分为同步任务(synchronous)和异步任务(asynchronous)
  • 同步任务都在 JS引擎线程(主线程) 上执行,形成一个执行栈(call stack)
  • 事件触发线程 管理一个 任务队列(Task Queue)
  • 异步任务 触发条件达成,将 回调事件 放到任务队列(Task Queue)中
  • 执行栈中所有同步任务执行完毕,此时JS引擎线程空闲,系统会读取任务队列,将可运行的异步任务回调事件添加到执行栈中,开始执行

当一个JS文件第一次执行的时候,js引擎会 解析这段代码,并将其中的同步代码 按照执行顺序加入执行栈中,然后从头开始执行。如果当前执行的是一个方法,那么js会向执行栈中添加这个方法的执行环境,然后进入这个执行环境继续执行其中的代码。当这个执行环境中的代码 执行完毕并返回结果后,js会退出这个执行环境并把这个执行环境销毁,回到上一个方法的执行环境。这个过程反复进行,直到执行栈中的代码全部执行完毕。

栗子

//(1)
console.log(1)
//(2)
setTimeout(()=>{
    console.log(2)
},100)
//(3)
console.log(3)

1.先解析整段代码,按照顺序加入到执行栈中,从头开始执行

2.先执行(1),是同步的,所以直接打印 1

3.执行(2),发现是 setTimeout,于是调用浏览器的方法(webApi)执行,在 100ms后将 console.log(2) 加入到任务队列

4.执行(3),同步的,直接打印 3

5.执行栈已经清空了,现在检查任务队列,(执行太快的话可能此时任务队列还是空的,没到100ms,还没有将(2)的打印加到任务队列,于是不停的检测,直到队列中有任务),发现有 console.log(2),于是添加到执行栈,执行console.log(2),同步代码,直接打印 2 (如果这里是异步任务,同样会再走一遍循环:-->任务队列->执行栈)

所以结果是 1 3 2;

注意:setTimeout/Promise等我们称之为任务源。而进入任务队列的是他们指定的回调;

2)宏任务(macro task)与微任务(micro task)

上面的循环只是一个宏观的表述,实际上异步任务之间也是有不同的,分为 宏任务(macro task) 与 微任务(micro task),最新的标准中,他们被称为 task与 jobs

  • 宏任务有哪些:script(整体代码), setTimeout, setInterval, setImmediate,I/O, UI rendering(渲染)
  • 微任务有哪些:process.nextTick, Promise, Object.observe(已废弃), MutationObserver(html5新特性)

下面我们再详细讲解一下执行过程:

执行栈在执行的时候,会把宏任务放在一个宏任务的任务队列,把微任务放在一个微任务的任务队列,在当前执行栈为空的时候,主线程会 查看微任务队列是否有事件存在。如果微任务队列不存在,那么会去宏任务队列中 取出一个任务 加入当前执行栈;如果微任务队列存在,则会依次执行微任务队列中的所有任务,直到微任务队列为空(同样,是吧队列中的事件加到执行栈执行),然后去宏任务队列中取出最前面的一个事件加入当前执行栈...如此反复,进入循环。

注意:

  • 宏任务和微任务的任务队列都可以有多个
  • 当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件。同一次事件循环中,微任务永远在宏任务之前执行。
  • 不同的运行环境 循环策略可能有不同,这里探讨chrome、node环境

 栗子  

//(1)
setTimeout(()=>{
    console.log(1)   // 宏任务
},100)
//(2)
setTimeout(()=>{
    console.log(2)  // 宏任务
},100)
//(3)
new Promise(function(resolve,reject){
    //(4)
    console.log(3)  // 直接打印
    resolve(4)
}).then(function(val){
    //(5)
    console.log(val); // 微任务
})
//(6)
new Promise(function(resolve,reject){
    //(7)
    console.log(5)   // 直接打印
    resolve(6)
}).then(function(val){
    //(8)
    console.log(val);  // 微任务
})
//(9)
console.log(7)  // 直接打印
//(10)
setTimeout(()=>{
    console.log(8) // 宏任务,单比(1)(2)宏任务早
},50)

上面的代码在node和chrome环境的正确打印顺序是 3 5 7 4 6 8 1 2

下面分析一下执行过程:

1.全部代码在解析后加入执行栈

2.执行(1),宏任务,调用webapi setTimeout,这个方法会在100ms后将回调函数放入宏任务的任务队列

3.执行(2),同(1),但是会比(1)稍后一点

4.执行(3),同步执行new Promise,然后执行(4),直接打印 3 ,然后resolve(4),然后.then(),把(5)放入微任务的任务队列

5.执行(6),同上,先打印 5 ,再执行resolve(6),然后.then()里面的内容(8)加入到微任务的任务队列

6.执行(9),同步代码,直接打印 7

7.执行(10),同(1)和(2),只是时间更短,会在 50ms 后将回调 console.log(8) 加入宏任务的任务队列

8.现在执行栈清空了,开始检查微任务队列,发现(5),加入到执行栈执行,是同步代码,直接打印 4

9.任务队列又执行完了,又检查微任务队列,发现(8),打印 6

10.任务队列又执行完了,检查微任务队列,没有任务,再检查宏任务队列,此时如果超过了50ms的话,会发现 console.log(8) 在宏任务队列中,于是执行 打印 8

11.依次打印 1 2

:因为渲染也是宏任务,需要在一次执行栈执行完后才会执行渲染,所以如果执行栈中同时有几个同步的改变同一个样式的代码,在渲染时只会渲染最后一个。

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • 浅谈JS三座大山之异步和单线程

    单线程 但是我们在开发中,遇到请求网络,或者定时任务的时候,如果等待网络请求结束或者定时任务结束的时候再去做其他事情,这样页面就会卡住,所以js有异步机制解决这个问题. 异步 异步的特点是不会阻塞后面的代码执行,当同步任务执行完毕之后,再执行异步任务.相对的,同步会阻止代码执行.异步任务的应用主要有网络请求和定时任务. 异步是通过callback的方式实现的,在callback里面执行异步执行的代码,但是有一些场景比如我们有三个网络请求abc需要依次执行,在a的回调里发起b请求,在b的回调里发起

  • JavaScript的单线程和异步详细

    目录 一.任务队列 二.借以解释几个容易困惑的问题 1.setTimeout(f1,0)是什么鬼 2.Ajax请求是否异步 3.界面渲染线程是单独开辟的线程 三.如何利用浏览器的异步机制 四.异步的好处和适合的场景 前言: 说到JavaScript的单线程(single threaded)和异步(asynchronous),很多同学不禁会想,这不是自相矛盾么?其实,单线程和异步确实不能同时成为一个语言的特性.js选择了成为单线程的语言,所以它本身不可能是异步的,但js的宿主环境(比如浏览器,No

  • 单线程JavaScript实现异步过程详解

    前两天硬着头皮在部门内部做了一次技术分享,主题如题.索性整理成文章留个纪念! 要了解异步实现,首先我们得先了解: 同步 & 异步 同步:会逐行执行代码,会对后续代码造成阻塞,直至代码接收到预期的结果之后,才会继续向下执行任务. 异步:调用之后先不管结果,继续向下执行任务. 网上各种文章对同步和异步的解释也不外如是,但是看文字总是有点晦涩难懂!我就生活化的来比拟一下这两个概念吧! 就好比请人吃饭: 比如你要请两个人吃饭,一个是巴菲特,由于他是举世瞩目股神想请他吃饭的人从这里排到了法国,你为表诚意,

  • 分析JS单线程异步io回调的特性

    我们最开始接触javascript应该大部分是从html中的js脚本开始,但是这种看似简单的语言稀里糊涂的用了好几年,也没有搞清楚它的一些原理机制,有没有躺枪! 起码javascript在操作dom的时候用了各种事件回调,比如按钮,链接的点击,鼠标经过,获取焦点等等. 在这个过程中,我们在dom上绑定一个事件回调函数 比如 onclick="doCheck()" 这个过程就是给dom元素注册了一个click 事件,并且绑定了一个事件回调函数 doCheck(). 当鼠标点击这个元素的时

  • JavaScript的三座大山之单线程和异步

    目录 一.进程与线程 1. 进程: 2. 线程: 3. 进程与线程的关系: 4. 浏览器运行是单进程还是多进程? 5. 如何查看浏览器是否是多进程运行的呢? 6. 浏览器运行是单线程还是多线程? 二.单线程 1.什么是单线程 2. JavaScript为什么是单线程 三.同步与异步 1.JS的 同步任务/异步任务 2. JavaScript为什么需要异步 3.JavaScript怎么实现异步 1)执行栈与任务队列 2)宏任务(macro task)与微任务(micro task) 总结 一.进程

  • JavaScript事件循环同步任务与异步任务

    目录 前言 执行栈与任务队列 执行栈 任务队列 同步任务与异步任务 同步任务 异步任务 js的执行机制 结语 前言 首先,在学习js中同步异步的问题前,需要明白,js是单线程的,为什么它得是单线程的呢?这得从它的使用场景来看了,它主要是用来让用户与页面进行交互的吧.那么假设js是多线程的,在这个线程里面,用户点击某个按钮会增加一个DOM节点,在另一个线程里面,用户点击这个按钮又会删除一个DOM节点,那么此时js就不知道该听谁的了.那同步异步的出现又是为了什么呢?假设没有异步,那么我们在向服务器请

  • Flutter 异步编程之单线程下异步模型图文示例详解

    目录 一. 本专栏图示概念规范 1. 任务概念规范 2. 任务的状态 3. 时刻与时间线 4.同步与异步 二.理解单线程中的异步任务 1. 任务的分配 2.异步任务特点 3. 异步任务完成与回调 三. Dart 语言中的异步 1.编程语言中与异步模型的对应关系 2.Dart 编程中的异步任务 3.当前任务分析 四.异步模型的延伸 1. 单线程异步模型的局限性 2. 多线程与异步的关系 3. Dart 中如何解决单线程异步模型的局限性 一. 本专栏图示概念规范 本专栏是对 异步编程 的系统探索,会

  • 理解javascript定时器中的单线程

    一.JavaScript 引擎是单线程的 可以从下面的代码中看到,第一个用setTimeout中的代码是死循环,由于是单线程,下面的两个定时器就没机会执行了. <script type="text/javascript"> setTimeout( function(){ while(true){} } , 100); setTimeout( function(){ alert('你好!setTimeout'); } , 200); setInterval( function

  • 深入理解JavaScript编程中的同步与异步机制

    JavaScript的优势之一是其如何处理异步代码.异步代码会被放入一个事件队列,等到所有其他代码执行后才进行,而不会阻塞线程.然而,对于初学者来说,书写异步代码可能会比较困难.而在这篇文章里,我将会消除你可能会有的任何困惑. 理解异步代码 JavaScript最基础的异步函数是setTimeout和setInterval.setTimeout会在一定时间后执行给定的函数.它接受一个回调函数作为第一参数和一个毫秒时间作为第二参数.以下是用法举例: console.log( "a" );

  • python 单线程和异步协程工作方式解析

    在python3.4之后新增了asyncio模块,可以帮我们检测IO(只能是网络IO[HTTP连接就是网络IO操作]),实现应用程序级别的切换(异步IO).注意:asyncio只能发tcp级别的请求,不能发http协议. 异步IO:所谓「异步 IO」,就是你发起一个 网络IO 操作,却不用等它结束,你可以继续做其他事情,当它结束时,你会得到通知. 实现方式:单线程+协程实现异步IO操作. 异步协程用法 接下来让我们来了解下协程的实现,从 Python 3.4 开始,Python 中加入了协程的概

  • javascript 支持链式调用的异步调用框架Async.Operation

    复制代码 代码如下: Async = {}; Async.Operation = function(options) { options = options || {}; var callbackQueue = []; var chain = (options.chain && options.chain === true) ? true : false; var started = false; var innerChain = null; this.result = undefined

  • 跟我学Nodejs(一)--- Node.js简介及安装开发环境

    学习资料 1.深入浅出Node.js 2.Node.js开发指南 简介(只捡了我觉得重要的) Node.js是让Javascript脱离浏览器运行在服务器的一个平台,不是语言: Node.js采用的Javascript引擎是来自Google Chrome的V8:运行在浏览器外不用考虑头疼的Javascript兼容性问题 采用单线程.异步IO与事件驱动的设计来实现高并发(异步事件也在一定程度上增加了开发和调试的难度): Node.js内建一个HTTP服务器,所以对于网站开发来说是一个好消息:  

  • JavaScript三大重点同步异步与作用域和闭包及原型和原型链详解

    目录 1. 同步.异步 2. 作用域.闭包 闭包 作用域 3. 原型.原型链 原型(prototype) 原型链 如图所示,JS的三座大山: 同步.异步 作用域.闭包 原型.原型链 1. 同步.异步 JavaScript执行机制,重点有两点: JavaScript是一门单线程语言 Event Loop(事件循环)是JavaScript的执行机制 JS为什么是单线程 最初设计JS是用来在浏览器验证表单操控DOM元素的是一门脚本语言,如果js是多线程的,那么两个线程同时对一个DOM元素进行了相互冲突

随机推荐