11、手把手教 Vue--彻底搞懂 JS 异步编程

11、手把手教 Vue--彻底搞懂 JS 异步编程_第1张图片
本节大纲

PS:转载请注明出处
作者: TigerChain
地址: https://www.jianshu.com/p/876e68fd6a1c
本文出自 TigerChain 手把手教 Vue 系列

教程简介

  • 1、阅读对象
    本篇教程适合新手阅读,老手直接略过
  • 2、教程难度
    初级,本人水平有限,文章内容难免会出现问题,如果有问题欢迎指出,谢谢

正文

不知你有没有听说过 js 是一门单线程、非阻塞、异步、并发语言「或者你常常给别人也这样说」。不晓得大伙听到这句话的时候有没有什么疑问,笔者当初听到这句话的时候---W T F 矛盾,天大的矛盾,主要疑惑有三点

1、单线程怎么可能异步「一个线程你玩个毛的异步」?
2、单线程是顺序执行的,非阻塞,一定会阻塞开国际玩笑呢?
3、单线程还并发,想不通,实在想不通?

哪怕你只是听说 js 是单线程的,但是能想到以上 3 点之一,都说明你是一个爱思考的童鞋

首先确定一下,上面那句话是正确无疑的,那话是正确的,既然话没有问题,那有问题的肯定是我们了,我们理解的还不够深刻,接下来我们就一步步揭开 js 的神秘面纱吧,首先我们看看同步和异步的区别

一、同步和异步

同步

同步指的是任务是一个接一个的去完成,上一个任务没有完成,下一个任务就不能开始,单线程和多线程都可以实现同步,但是单线程一定是同步的「一个线程只有执行完前面的任务,才能执行后面的」

11、手把手教 Vue--彻底搞懂 JS 异步编程_第2张图片
sync

异步

异步是一个相对概念,多线程是异步的前提,一个线程是玩不了异步的

11、手把手教 Vue--彻底搞懂 JS 异步编程_第3张图片
async

比如 Java 语言,声明一个 Thread 看起来只有一个线程,但是调用 start() 方法却异步执行了,请看图

11、手把手教 Vue--彻底搞懂 JS 异步编程_第4张图片
java-async-thread

执行结果

show-java-thread-result.png

从结果来看 java 默认是有一个主线程的「main 线程,上面的 thrad 异步就是相对于 main 而言的」,所以根本不可能一个线程就能完成异步

那么到底 js 是如何实现异步的呢?说异步我们不得不说以下几个角色
JavaScript Engine、Web APIs、Message queue、Event loop,接下来一一介绍,首先登场的是 JS 引擎

二、JavaScript Engine

JS 引擎有的也称为 JS 虚拟机,主要是负责解析和执行 js 的,它是浏览器所实现的,不同的浏览器有不同的实现方式「采用 c/c++ 实现」,这里以 V8 引擎为例来说明「其它的引擎都大同小异」

来看看引擎的简易图

11、手把手教 Vue--彻底搞懂 JS 异步编程_第5张图片
js-engine

由图可知,JS 引擎主要包括两个组件就是堆和栈

堆: 用就是用来分配内存的地方

栈: 也叫 调用栈/执行栈 就是方法调用和执行的地方「js 是单线程说的就是 call stack 」

这里顺便说一下,浏览器有渲染引擎和 js 引擎,浏览器是从上向下解析 html 标签的,当遇到 script 标签「js 代码」时会立即停止解析,直接执行 js 脚本,所以渲染引擎和 js 引擎是互斥的「js 操作 DOM 的会影响渲染」,这一个过程是同步的,所以加载一个耗时的 js 会导致界面卡死的根本原因就在这里,有兴趣的可以看看浏览器的渲染引擎这方面内容,或者后面我专写一篇此类文章「扯的远了,脉动回来」

来看看 stack 的执行机制

先来一段代码

11、手把手教 Vue--彻底搞懂 JS 异步编程_第6张图片
call-statck-js-demo

这段代码本身没有什么好说的,非常简单的代码,我们看看 js 引擎在执行这段代码的时候 call stack 中的执行过程

11、手把手教 Vue--彻底搞懂 JS 异步编程_第7张图片
call-stack-exec-procress

call stack 由名子可以看出它是一个栈结构「那肯定遵循先进后出原则」,当一个方法调用的时候就入栈,执行完成以后就出栈

再来一段暴力代码

11、手把手教 Vue--彻底搞懂 JS 异步编程_第8张图片
loop-call-js

以上代码是一段暴力代码,就是一个死循环,我们来看看结果

11、手把手教 Vue--彻底搞懂 JS 异步编程_第9张图片
call-statck-out

由结果可行,call stack 栈大小被撑爆了,其实可以想像,不停的调用 hello 方法,入栈、入栈 ... 入栈,肯定最后就放不下了

栈有多大?

由上面的死循环代码我们就可以尝试着算出 call stack 的大小参考 2ality.com

11、手把手教 Vue--彻底搞懂 JS 异步编程_第10张图片
cal-call-stack-size

当然对不同的浏览器结果是不一样的,引擎实现的方式不一样,我测试在 chorome 如下「不同浏览器大家可自行测试一下」

show-cal-call-stack-size-result

我们清楚了当调用一个方法的时候 js 引擎会把方法压入 call stack ,当方法执行完毕以后出栈

三、Web APIs

由于 js 引擎中的 stack 同一时间只能干一件事情「单线程」,那么 call stack 肯定是玩不了异步,可是虽然 js 是单线程的,但浏览器却是多线程的,我们知道 js 有好多 API 有些不是核心 js 语言的一部分,比如 BOM DOM AJAX setTimeOut Canvas WegGl 等 api 浏览器可以在调用之外执行这些 api 「另起一个或多个线程跑这些 api」

11、手把手教 Vue--彻底搞懂 JS 异步编程_第11张图片
web-api

这些 api 就可以独立于调用栈来执行自己的功能,但是有一个问题是如果这些 api 执行完以后该怎么办呢?有两种方案

  • 1、我们将 web api 完成的方法直接推送到调用栈
  • 2、我们采取一些机制来保存这些响应,在合适的时候推送给调用栈

第 1 种方法显然不靠谱,如果 web api 执行完以后直接把结果给调用栈可以会影响正在执行的调用栈,所以浏览器采用第二种方法,使用消息队列来保存这些 web api 执行的响应以便在调用栈可以调用的时候推送给调用栈,这个保存消息的东西就是接下来我们要说的 Message Queue

四、Message Queue

Message Queue「消息队列也叫 Callback Queue」是用来保存 Web Api 调用完成以后的所有消息的回调函数,当调用栈「call stack」为空时「也就是调用栈中的方法执行完毕以后」Message Queue 中的回调方法「先进先出」会被添加到调用栈中去执行,但是浏览器是什么方式来把调用栈和 message Queue 联系起来的「什么机制把 Message Queue 中的回调方法给 call stack 当 call statck 为空的时候」,它就是 Event Loop

11、手把手教 Vue--彻底搞懂 JS 异步编程_第12张图片
message-queue

五、Event Loop

Event Loop 是把 call stack 和 Message Queue 联系起来的纽带和桥梁,Event Loop 是一个基于事件的并发模型,它时刻在监听着消息队列,如果有完成的消息它此刻还要关心 call stack 是否为空,如果为空则把 Messag Queue 中的回调结果推送给 call statck 回调方法执行

Event Loop 做两件事情

  • 1、监听 Message Queue「是否有消息」
  • 2、监听 call statck 「看是否为空,如果为空则推送结果」
11、手把手教 Vue--彻底搞懂 JS 异步编程_第13张图片
event-loop.jpg

这样就完成了 js 的非阻塞异步调用

六、代码来分析异步调用过程

写一段如下代码

11、手把手教 Vue--彻底搞懂 JS 异步编程_第14张图片
async-run-procress

非常简单的一段代码,体现了 js 的异步过程

分析过程

11、手把手教 Vue--彻底搞懂 JS 异步编程_第15张图片
js-async-fx.jpg

上图显示了上述代码的执行过程,简单的说一下吧,上述代码分为十个步骤

  • 1、当代码执行 console.log('大家好!')的时候此方法入栈,这个没有什么好说的,前面说过了
  • 2、方法继续向下执行遇到 setTimeout()方法,这是一个 webapi 方法然后交给 3 去执行
  • 3、浏览器单独开一个线程去执行,然后调用栈不停止继续向下执行
  • 4、调用栈执行 console.log('欢迎关注') 方法
  • 5、web api 执行 setTimeout 方法完毕,指导结果给 Message Queue ,此是 webpai 就变成空的「图上没有体现,希望明白」
  • 6、此时调用栈中执行完 console.log('欢迎关注') 以后此方法出栈,栈此时变成空的,Event Loop 监听着 Message Queue
  • 7、Event loop 把 Message Queue 中的方法取出来,推给空的调用栈,此时 callback 入栈「调用栈」
  • 8、执行其中的方法体 console.log('TigerChain')
  • 9、打印了 TigerChain 此方法出栈
  • 10、到此 callback 执行完毕,callback 出栈,调用栈变为空

以上过程只是一个针对简单代码的一个简单的分析,如果存在多个异步操作,则 Event Loop 不停的执行取出消息推入栈的操作直到完成

七、总结

到此我们把 js 非阻塞和异步的原理大概说了一下,相信大家应该有一个简单的知识和了解,大概总结一下

  • 1、js 是非阻塞异步的单线程「单线程指的就是 call stack」
  • 2、js 实现异步的方式是基于 Event Loop 的并发模型
  • 3、浏览器的 web api 不是 js 核心的部分,但是和 call stack 不冲突执行「浏览器另外开线程去执行」
  • 4、web api 的执行结果不能直接给 call stack 先要通过 Message Queu 把结果存起来,等待 Event Loop 去处理
  • 5、Event Loop 如果发现 call statck 为空时「此时就是推入 Message Queue 中的消息的最佳时机」取出消息队列中的消息推入给调用栈,异步结束

我们来看一张非常形象的 Event Loop 并发模型的图

11、手把手教 Vue--彻底搞懂 JS 异步编程_第16张图片
event-loop-pic

图片来源 The Main Event… Loop 建议把这张图印在心里、印在心、印在心里「重要的事情说三遍」

js 的异步执行过程从上图非常形象的展现出来,从 statck 开始顺时针执行,遇到 webpai 方法让其去调用并把结果给 Message Queue , Event Loop 查看 stack 为空则取出 Message Queue 中的方法给 statck 搞懂此图就彻底了解了 Event Loop 的并发模型了

本节就到此结束了,源码就不给了「非常简单的例子」,自行照着敲一下比什么都好

八、参考资料

  • 1、The Main Event… Loop
  • 2、JavaScript: Execution of Synchronous and Asynchronous codes

更多文章关注:手把手教Vue系列


点赞富一生「你一点赞我就更来劲了」,转发富五代,更多文章请关注我的微信公号来查阅

公众号:TigerChain

你可能感兴趣的:(11、手把手教 Vue--彻底搞懂 JS 异步编程)