11、手把手教 Vue--彻底搞懂 JS 异步编程
PS:转载请注明出处
作者: TigerChain
地址: https://www.jianshu.com/p/876e68fd6a1c
本文出自 TigerChain 简书 手把手教 Vue 系列
教程简介
- 1、阅读对象
本篇教程适合新手阅读,老手直接略过 - 2、教程难度
初级,本人水平有限,文章内容难免会出现问题,如果有问题欢迎指出,谢谢
正文
不知你有没有听说过 js 是一门单线程、非阻塞、异步、并发语言「或者你常常给别人也这样说」。不晓得大伙听到这句话的时候有没有什么疑问,笔者当初听到这句话的时候---W T F 矛盾,天大的矛盾,主要疑惑有三点
1、单线程怎么可能异步「一个线程你玩个毛的异步」?
2、单线程是顺序执行的,非阻塞,一定会阻塞开国际玩笑呢?
3、单线程还并发,想不通,实在想不通?
哪怕你只是听说 js 是单线程的,但是能想到以上 3 点之一,都说明你是一个爱思考的童鞋
首先确定一下,上面那句话是正确无疑的,那话是正确的,既然话没有问题,那有问题的肯定是我们了,我们理解的还不够深刻,接下来我们就一步步揭开 js 的神秘面纱吧,首先我们看看同步和异步的区别
一、同步和异步
同步
同步指的是任务是一个接一个的去完成,上一个任务没有完成,下一个任务就不能开始,单线程和多线程都可以实现同步,但是单线程一定是同步的「一个线程只有执行完前面的任务,才能执行后面的」
sync异步
异步是一个相对概念,多线程是异步的前提,一个线程是玩不了异步的
async比如 Java 语言,声明一个 Thread 看起来只有一个线程,但是调用 start() 方法却异步执行了,请看图
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 引擎为例来说明「其它的引擎都大同小异」
来看看引擎的简易图
js-engine由图可知,JS 引擎主要包括两个组件就是堆和栈
堆: 用就是用来分配内存的地方
栈: 也叫 调用栈/执行栈 就是方法调用和执行的地方「js 是单线程说的就是 call stack 」
这里顺便说一下,浏览器有渲染引擎和 js 引擎,浏览器是从上向下解析 html 标签的,当遇到 script 标签「js 代码」时会立即停止解析,直接执行 js 脚本,所以渲染引擎和 js 引擎是互斥的「js 操作 DOM 的会影响渲染」,这一个过程是同步的,所以加载一个耗时的 js 会导致界面卡死的根本原因就在这里,有兴趣的可以看看浏览器的渲染引擎这方面内容,或者后面我专写一篇此类文章「扯的远了,脉动回来」
来看看 stack 的执行机制
先来一段代码
call-statck-js-demo这段代码本身没有什么好说的,非常简单的代码,我们看看 js 引擎在执行这段代码的时候 call stack 中的执行过程
call-stack-exec-procresscall stack 由名子可以看出它是一个栈结构「那肯定遵循先进后出原则」,当一个方法调用的时候就入栈,执行完成以后就出栈
再来一段暴力代码
loop-call-js以上代码是一段暴力代码,就是一个列循环,我们来看看结果
call-statck-out由结果可行,call stack 栈大小被撑爆了,其实可以想像,不停的调用 hello 方法,入栈、入栈 ... 入栈,肯定最后就放不下了
栈有多大?
由上面的死循环代码我们就可以尝试着算出 call stack 的大小参考 2ality.com
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」
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
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 「看是否为空,如果为空则推送结」
这样就完成了 js 的非阻塞异步调用
六、代码来分析异步调用过程
写一段如下代码
async-run-procress非常简单的一段代码,体现了 js 的异步过程
分析过程
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 并发模型的图
event-loop-pic图片来源 The Main Event… Loop 建议把这张图印在心里、印在心、印在心里「重要的事情说三遍」
js 的异步执行过程从上图非常形象的展现出来,从 statck 开始顺时针执行,遇到 webpai 方法让其去调用并把结果给 Message Queue , Event Loop 查看 stack 为空则取出 Message Queue 中的方法给 statck 搞懂此图就彻底了解了 Event Loop 的并发模型了
本节就到此结束了,源码就不给了「非常简单的例子」,自行照着敲一下比什么都好
八、参考资料
更多文章关注:手把手教Vue系列
点赞富一生「你一点赞我就更来劲了」,转发富五代,更多文章请关注我的微信公号来查阅
公众号:TigerChain