让前端飞测试框架node

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

2018-10-18  本文已影响112人  TigerChain
本节大纲

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

教程简介

正文

不知你有没有听说过 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-procress

call 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 执行完以后直接把结果给调用栈可以会影响正在执行的调用栈,所以浏览器采用第二种方法,使用消息队列来保存这些 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 做两件事情

event-loop.jpg

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

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

写一段如下代码

async-run-procress

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

分析过程

js-async-fx.jpg

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

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

七、总结

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

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

event-loop-pic

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

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

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

八、参考资料

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


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

公众号:TigerChain

上一篇下一篇

猜你喜欢

热点阅读