前言

2014年秋季写完了《深入理解javascript原型和闭包系列》,已经帮助过很多人走出了 js 原型、作用域、闭包的困惑,至今仍能经常受到好评的留言。

很早之前我就总结了JS三座大山这个概念(虽然没有到处宣扬),前两座(原型、作用域)已经基本讲明白,而第三座(异步)也应该做一个总结。

于是,2017年初春,我花费大约一周的业余时间来对 JS 异步做一个完整的总结,和各位同学共勉共进步!

原文地址:http://www.cnblogs.com/wangfupeng1988/p/6513070.html 未经同意禁止转载!

第一部分,什么是异步

提醒:如果你是初学 js 的同学,尚未有太多项目经验和基础知识,请就此打住,不要看这篇教程

我思考问题、写文章一般都不按讨论出牌,别人写过的东西我不会再照着抄一遍。因此,后面所有的内容,都是我看了许多资料之后,个人重新思考提炼总结出来的,这肯定不能算是初级教程。

如果你是已有 js 开发经验,并了解异步的基础知识,到这里来想深入了解一下Promise Generatorasync-awati,那就太好了,非常欢迎。

本节内容概述

  • JS 为何会有异步
  • 异步的实现原理是什么
  • 常用的异步操作有哪些

JS 为何会有异步

首先记住一句话 —— JS 是单线程的语言,所谓“单线程”就是一根筋,对于拿到的程序,一行一行的执行,上面的执行为完成,就傻傻的等着。例如

  1. var i, t = Date.now()
  2. for (i = 0; i < 100000000; i++) {
  3. }
  4. console.log(Date.now() - t) // 250 (chrome浏览器)

上面的程序花费 250ms 的时间执行完成,执行过程中就会有卡顿,其他的事儿就先撂一边不管了。

执行程序这样没有问题,但是对于 JS 最初使用的环境 ———— 浏览器客户端 ———— 就不一样了。因此在浏览器端运行的 js ,可能会有大量的网络请求,而一个网络资源啥时候返回,这个时间是不可预估的。这种情况也要傻傻的等着、卡顿着、啥都不做吗?———— 那肯定不行。

因此,JS 对于这种场景就设计了异步 ———— 即,发起一个网络请求,就先不管这边了,先干其他事儿,网络请求啥时候返回结果,到时候再说。这样就能保证一个网页的流程运行。

异步的实现原理

先看一段比较常见的代码

  1. var ajax = $.ajax({
  2. url: '/data/data1.json',
  3. success: function () {
  4. console.log('success')
  5. }
  6. })

上面代码中$.ajax()需要传入两个参数进去,urlsuccess,其中url是请求的路由,success是一个函数。这个函数传递过去不会立即执行,而是等着请求成功之后才能执行。对于这种传递过去不执行,等出来结果之后再执行的函数,叫做callback,即回调函数

再看一段更加能说明回调函数的 nodejs 代码。和上面代码基本一样,唯一区别就是:上面代码时网络请求,而下面代码时 IO 操作。

  1. var fs = require('fs')
  2. fs.readFile('data1.json', (err, data) => {
  3. console.log(data.toString())
  4. })

从上面两个 demo 看来,实现异步的最核心原理,就是将callback作为参数传递给异步执行函数,当有结果返回之后再触发 callback执行,就是如此简单!

常用的异步操作

开发中比较常用的异步操作有:

  • 网络请求,如ajax http.get
  • IO 操作,如readFile readdir
  • 定时函数,如setTimeout setInterval

最后,请思考,事件绑定是不是也是异步操作?例如$btn.on('click', function() {...})。这个问题很有意思,我会再后面的章节经过分析之后给出答案,各位先自己想一下。

第二部分,异步和 event-loop

提到异步,就必须提 event-loop 。event-loop 中文翻译叫做“事件轮询”,它是能体现出单线程中异步操作是如何被执行的。

首先,强烈大家观看一个歪果仁的视频《what the hack is event loop,只有不到半个小时的时间,但是将的非常详细。如果那个链接失效,访问这里(密码: xx9f)

其次,再结合阮一峰老师的《什么是event loop》一起看一下。将这两个看完就基本了解 event loop 了

最后,event-loop 是一块内容比较独立的技术性知识,它是什么样子就是什么样子,讲解起来可变通性非常小。因此,本节说一下我对 event-loop 的理解和体会

本节内容概述

  • 举例说明
  • 核心概念
  • 思考两个问题

举例说明

给出一段简单的 js 代码,并用比较通俗、简单的说法介绍一下执行过程。详细过程还需各位去看视频,因为我没必要把半小时的视频都写到这里。

  1. console.log('line 1')
  2. setTimeout(console.log, 1000, 'line 2')
  3. console.log('line 3')

以上一共三行代码,该程序被执行的时候,会依次挨行执行

  • 第一步,执行第一行,将结果line 1打印出来
  • 第二步,执行第二行,注意此时会将这个操作暂时存储到其他地方,因为setTimeout是一个异步执行操作。
  • 第三步,执行第三行,将结果line 3打印出出来
  • 第四步,等待最后一行程序(一共三行)都全部执行完了,然后立马实时查看刚才暂存的异步操作有没有。如果有可执行的,就立即拿到出来继续执行。
  • 第五步,执行完毕之后,再实时查看暂存位置中是否还有未执行的异步回调。

以上只拿了setTimeout举例子,但是对于网络请求、IO操作、事件绑定道理都是一样的。如果我讲的简单例子你还是看不懂,一定要去看文章最初提到的《what the hack is event loop》视频,重要重要!!!

思考三个问题

第一题,以下代码的输出顺序是什么

  1. setTimeout(console.log, 0, 'a')
  2. console.log('b')
  3. console.log('c')

答案是b c a,有疑问的需要再去看上面的介绍或者那个视频。

第二题,以下代码中,最后输出的结果是否是 500

  1. var i, t = Date.now()
  2. for (i = 0; i < 100000000; i++) {
  3. }
  4. function fn() {
  5. console.log(Date.now() - t) // 输出多少???
  6. }
  7. setTimeout(fn, 500)

答案是大于 500ms ,因为 for 函数需要花费一些时间,等 for 执行完之后再开始计算 500ms 之后执行 fn

第三题,事件绑定是不是异步操作?

这个问题大家根据 event-loop 的讲解和视频来思考,我们下一节再给出解答。

第三部分,事件绑定算不算异步?

如果你认真看了上一节的 event-loop 的,你会发现原来事件绑定和异步操作的实现机制是一样的,那么事件绑定是不是就是异步操作呢?(声明一下,这里说的事件绑定是如下代码的形式)

  1. $btn.on('click', function (e) {
  2. console.log('你点击了按钮')
  3. })

PS:这个问题貌似没有加过有人讨论或者发起讨论,但是当我了解了 event-loop 之后,我就发现这两者有很大联系,很早就像讨论一下这个话题。不知道哪位同仁跟我有一样的想法?

本节内容概述

  • 共同之处
  • 不同之处
  • 我的观点

共同之处

从技术实现以及书写方法上来讲,他们是一样的。例如事件绑定和 IO 操作的写法基本相同

  1. $btn.on('click', function (e) {
  2. console.log('你点击了按钮')
  3. })
  4. fs.readFile('data1.json', function (err, data) {
  5. // 获取数据
  6. })

最终执行的方式也基本一样,都通过 evet-loop 执行。

不同之处

在我看来至少有两处不同。

第一,event-loop 执行时,调用的源不一样。异步操作是系统自动调用,无论是setTimeout时间到了还是$.ajax请求返回了,系统会自动调用。而事件绑定就需要用户手动触发

第二,从设计上来将,事件绑定有着明显的“订阅-发布”的设计模式,而异步操作却没有。

我的观点

我个人看代码比较偏重设计,一个东西是什么要看它是未什么而设计的。因此,我倾向于事件绑定不是异步操作。虽然它也是通过 event-loop 实现调用的,但是它的设计目录却和异步操作完全不一样。

其实,事件绑定在 js 中扮演着非常重要的角色,各个地方都会用到事件绑定的形式。例如 web 页面监控鼠标、键盘,以及 nodejs 中的 EventEmitter 应用非常广泛(特别是涉及到数据流时)。而事件绑定被应用到非常广泛,却没有发生像异步操作带来的程序逻辑问题,反而大家用的非常开心————这又一个两者不一样的例证。

如果你觉得我的观点有问题,也可以大胆提出自己的建议和意见,发表出来!说对说错都无所谓,也不会扣你落户积分,只要能自圆其说就是好的。

求打赏

如果你看完了,感觉还不错,欢迎给我打赏 ———— 以激励我更多输出优质内容

最后,github地址是 https://github.com/wangfupeng1988/js-async-tutorial 欢迎 star 和 pr

-------

学习作者教程:《前端JS高级面试》《前端JS基础面试题》《React.js模拟大众点评webapp》《zepto设计与源码分析》《json2.js源码解读

深入理解 JavaScript 异步系列(1)——基础的更多相关文章

  1. 深入理解 JavaScript 异步系列(1)—— 什么是异步

    前言 2014年秋季写完了<深入理解javascript原型和闭包系列>,已经帮助过很多人走出了 js 原型.作用域.闭包的困惑,至今仍能经常受到好评的留言. 很早之前我就总结了JS三座大 ...

  2. 深入理解 JavaScript 异步系列(3)—— ES6 中的 Promise

    第一部分,Promise 加入 ES6 标准 原文地址 http://www.cnblogs.com/wangfupeng1988/p/6515855.html 未经作者允许不得转载! 从 jquer ...

  3. 深入理解 JavaScript 异步系列(4)—— Generator

    第一部分,ES6 中的 Generator 原文地址 http://www.cnblogs.com/wangfupeng1988/p/6532713.html 未经作者允许不得转载~ 在 ES6 出现 ...

  4. 深入理解 JavaScript 异步系列(5)—— async await

    第一部分,ES7 中引入 async-await 原文地址 http://www.cnblogs.com/wangfupeng1988/p/6532734.html 未经作者允许,不得转载~ 前面介绍 ...

  5. 深入理解 JavaScript 异步系列(2)—— jquery的解决方案

    第一部分,jQuery-1.5 之后的 ajax 本地址http://www.cnblogs.com/wangfupeng1988/p/6515779.html未经允许不得转载~ $.ajax这个函数 ...

  6. 深入理解 JavaScript 异步——转载

    本文章转载于深入理解 JavaScript 异步 前言 2014年秋季写完了<深入理解javascript原型和闭包系列>,已经帮助过很多人走出了 js 原型.作用域.闭包的困惑,至今仍能 ...

  7. 深入理解javascript作用域系列第二篇——词法作用域和动态作用域

    × 目录 [1]词法 [2]动态 前面的话 大多数时候,我们对作用域产生混乱的主要原因是分不清楚应该按照函数位置的嵌套顺序,还是按照函数的调用顺序进行变量查找.再加上this机制的干扰,使得变量查找极 ...

  8. 深入理解javascript作用域系列第二篇

    前面的话 大多数时候,我们对作用域产生混乱的主要原因是分不清楚应该按照函数位置的嵌套顺序,还是按照函数的调用顺序进行变量查找.再加上this机制的干扰,使得变量查找极易出错.这实际上是由两种作用域工作 ...

  9. 深入理解javascript函数系列第一篇——函数概述

    × 目录 [1]定义 [2]返回值 [3]调用 前面的话 函数对任何一门语言来说都是一个核心的概念.通过函数可以封装任意多条语句,而且可以在任何地方.任何时候调用执行.在javascript里,函数即 ...

随机推荐

  1. Vue 获取登录用户名

    本来是打算登录的时候把用户名传过去,试了几次都没成功,然后改成用cookie保存用户名,然后在读取就行了, 登录时候设置cookie setCookie(c_name,c_pwd,exdays) { ...

  2. FTP模式简式:PORT/PASV/EPRT/EPSV

    简介 常见FTP有两种模式:PORT(主动模式).PASV(被动模式). 而EPRT/EPSV模式出现的原因是FTP仅仅提供了建立在IPv4上进行数据通信的能力,它基于网络地址是32位这一假设.但是, ...

  3. ProcessingElement.h

    processing element模块 #ifndef __NOXIMPROCESSINGELEMENT_H__ #define __NOXIMPROCESSINGELEMENT_H__ #incl ...

  4. JavaWeb三大组件之Filter

    对请求或者响应进行拦截,并做额外的逻辑处理 filter能在一个请求访问目标资源之前对其进行拦截然后做某些逻辑处理,例如权限检查,也可以在一个输出响应到达客户端之前对其进行拦截并做一些额外的操作(例如 ...

  5. eclipse 无法记住svn密码

    每次要求输入密码,很恼人.经过一番折腾,在stackoverflow上找到了解决方案,上面大神果然多 简单的说,就是通过查看工作空间的日志文件,发现报了个java异常,缺少一个class文件(org. ...

  6. 计算机网络九:IP地址、子网掩码、默认网关、DHCP服务器、DNS服务器、WINS服务器

    一.IP地址与子网掩码 1.IP地址 ipv4下,ip地址=网络号+主机号. 2.子网掩码         子网掩码(subnet mask)又叫网络掩码.地址掩码.子网络遮罩,它是一种用来指明一个I ...

  7. 1.准备工作之Groovy

    Groovy(读做:gu : ru : wei) Groovy是一种运行在jvm上的动态语言,它吸取了Python.Ruby和SmallTalk等语言的优点:在Java的基础之上增加了许多特色功能,相 ...

  8. Settings 参数记录

    DOWNLOAD_FAIL_ON_DATALOSS : 参数:TRUE.FALSE 如果设置为 True : scrapy.Request 有一个 errback 参数, 当 Request 请求出错 ...

  9. 你可以这么理解五种I/O模型

    因为项目需要,接触和使用了Netty,Netty是高性能NIO通信框架,在业界拥有很好的口碑,但知其然不知其所以然. 所以本系列文章将从基础开始学起,深入细致的学习NIO.本文主要是介绍五种I/O模型 ...

  10. Java变量与运算

    变量 1.变量名可以使用 数字.字母.下划线.$符号.数字包括 '0'~'9' 和某种语言中表示数字的任何 Unicode 字符.字母包括 'A'~'Z'.'a'~'z' 和某种语言中表示字母的任何 ...