“同步请求”,“异步请求”相信这两词在程序猿的世界中频频出现,到底是词性的妖娆,还是撸代码的基础要求,下面直接分享本人学习的好东西,保证让你深入浅出,爽得不要不要的。

  一、单线程

  我们常说的“JavaScript是单线程的”。所谓单线程,是指在JS引擎中负责解释和执行JavaScript代码的线程只有一个,一般称它为主线程。但是实际上还存在其他的线程,例如:处理AJAX请求的线程、处理DOM事件的线程、定时器线程、读写文件的线程等等。这些线程可能存在于JS引擎之内,也可能存在于JS引擎之外,在此我们不做区分,不妨称它们为工作线程吧。

  

  二、同步和异步

  假设存在一个函数A:

  A(args...){

    ...

  };

  同步:如果在函数A返回的时候,调用者就能够得到预期的结果(即拿到了预期的返回值或者看到了预期的效果),那么这个函数就是同步的。

  例:1、 Math.sqrt(2);

    2、console.log('这是我们经常用的好东西');

    第一个函数返回时,就拿到了我们预期的返回值,2的平方;第二个函数返回时,就能看到预期的结果,我们在控制台打印了一个字符串,所以这两个函数都是同步的。

  异步:如果在函数A返回的时候,调用者还不能马上得到预期的结果,而是需要在将来通过一定的手段得到,那么这个函数就是异步的 。

  例:fs.readFile( 'foo.txt', 'utf8', function(err, data) {

      console.log(data);

    });

  在上面的栗子中,我们希望通过fs.readFile函数读取文件foo.txt中的内容,并打印出来。但是在fs.readFile函数返回值之前,我们期望的结果并不会发生,而是要等到文件全部读取完成之后(如果文件很大的话,可能需要的时间会长一点),这就是异步。

  下面以AJAX请求为例,来看一下同步和异步的区别:

  异步AJAX:

    主线程:“你好,AJAX线程,请你帮我发个HTTP请求吧,我把请求地址和参数都给你了。”

    AJAX线程:“好的,主线程。我马上去发,但是可能需要花点时间呢,你可以先去忙别的。”

    主线程:“谢谢,你拿到响应之后告诉我一声哈。”

    (注:主线程和AJAX线程都各自同步干活,一段时间后,主线程就能收到AJAX线程响应的通知,然后继续执行相应的工作)。

  同步AJAX:

    主线程:“你好,AJAX线程,请你帮我发个HTTP请求吧,我把请求地址和参数都给你了。”

    AJAX线程:“......”

    主线程:“喂,AJAX线程,你怎么不说话?”

    AJAX线程:“......” 

    主线程:“喂!喂喂喂!”

    AJAX线程:“......”

    (一炷香的时间后)

    主线程:“喂!求你说句话吧!”

    AJAX线程:“主线程,不好意思,我在工作的时候不能说话,你的请求已经发完了,拿到响应的数据了,给你。”

    (注:同步AJAX的主线程和AJAX线程不能同步干活,只能等AJAX线程干完活拿到响应后,只线程才能接着干活)

  正是由于JavaScript是单线程,而异步容易实现非阻塞,所以在JavaScript中对于耗时的操作或者时间不确定的操作,实用异步就成了必然的选择。

  三、异步过程的构成要素

  从上文可以看出,异步函数实际上很快就调用完成了,但是后面还有工作线程执行异步任务,通知主线程,主线程调用回调函数等很多步骤。我们把整个过程叫做异步过程,异步函数的调用在整个异步过程中只是一小部分。

  总结一下,一个异步过程的整个过程:主线程发一起一个异步请求,相应的工作线程接收请求并告知主线程已收到通知(异步函数返回);主线程可以继续执行后面的代码,同时工作线程执行异步任务;工作线程完成工作后,通知主线程;主线程收到通知后,执行一定的动作(调用回调函数)。

  异步函数通常具有以下的形式:A(args..., callbackFn);

  它可以叫做异步过程的发起函数,或者叫做异步任务注册函数。args是这个函数需要的参数,callbackFn(回调函数)也是这个函数的参数,但是它比较特殊所以单独列出来。所以,从主线程的角度看,一个异步过程包括下面两个要素:

  1、发起函数(或叫注册函数)A;

  2、回调函数callbackFn;

  它们都是主线程上调用的,其中注册函数用来发起异步过程,回调函数用来处理结果。

  举个具体的栗子:

  setTimeout(function,1000);

  其中setTimeout就是异步过程的发起函数,function是回调函数。

  注:前面说得形式A(args..., callbackFn)只是一种抽象的表示,并不代表回调函数一定要作为发起函数的参数,例如:

  var xhr = new XMLHttpRequest();

  xhr.onreadystatechange = xxx;     // 添加回调函数

  xhr.open('GET', url);

  xhr.send();   // 发起函数  

  发起函数和回调函数是分离的。

  四、消息队列和事件循环

  上文讲到,异步过程中,工作线程在异步操作完成后需要通知主线程。那么这个通知机制是怎样实现的呢?答案是利用消息队列和事件循环。

  工作线程将消息放到消息队列,主线程通过事件循环过程去取消息。

  消息队列:消息队列是一个先进先出的队列,它里面存放这各种消息。

  事件循环:事件循环是指主线程重复从消息队列中取消息,执行的过程。

  实际上,主线程只会做一件事情,就是从消息队列里面取消息、执行消息、再取消息、再执行消息。当消息队列为空时,就会等待直到消息队列变成非空。而且主线程只有在将前面的消息执行完成后,才会去去下一个消息。这种机制就叫做事件循环机制,取一个消息并执行的过程叫做一次循环。

  事件循环代码表示大概是这样的:

  while(true){

    var message = queue.get();

    execute(message);

  }

  那么,消息队列中放的消息具体是什么?消息的具体结构当然跟具体实现有关,但是为了简单起见,我们可以认为:消息就是注册异步任务时添加的回调函数。

  

  再次以异步AJAX为例,假设存在如下的代码:

  $.ajax('http://baidu.com',function(resp){

    console.log('我是响应',resp);

  })

  // 其他代码

  ...

  ...

  主线程在发起AJAX请求后,会继续执行其他代码,AJAX线程负责请求http://baidu.com,拿到响应后,它会把响应封装成一个JavaScript对象,然后构造一条消息:

  var message = function(){

    callbackFn(response);

  }

  其中callbackFn就是就是前面代码中得到成功响应时的回调函数。

  主线程在执行完当前循环中的所有代码后,就会到消息队列取出这条消息(也就是message函数),并执行它。到此为止,就完成了工作线程对主线程的通知,回调函数也就得到了执行。如果一开始主线程就没有提供回调函数,AJAX线程在收到HTTP响应后,也就没必要通知主线程,从而也没必要往消息队列放消息。

  用图表示这个过程就是:

  

  

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; line-height: 19.0px; font: 13.0px "Helvetica Neue" }
p.p2 { margin: 0.0px 0.0px 0.0px 0.0px; line-height: 19.0px; font: 13.0px "Helvetica Neue"; min-height: 15.0px }

  从上文中我们也可以得到这样一个明显的结论,就是:异步过程的回调函数,一定不在当前这一轮事件循环中执行。

  五. 异步与事件

  上文中说的“事件循环”,为什么里面有个事件呢?那是因为:消息队列中的每条消息实际上都对应着一个事件。上文中一直没有提到一类很重要的异步过程:DOM事件。

  举个栗子:

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; line-height: 19.0px; font: 13.0px "Helvetica Neue" }

  var button = document.getElement('#btn');

    button.addEventListener('click', function(e) {

    console.log();

  });

  从事件的角度来看,上述代码表示:在按钮上添加了一个鼠标单击事件的事件监听器;当用户点击按钮时,鼠标单击事件触发,事件监听器函数被调用。

  从异步过程的角度看,addEventListener函数就是异步过程的发起函数,事件监听器函数就是异步过程的回调函数。事件触发时,表示异步任务完成,会将事件监听器函数封装成一条消息放到消息队列中,等待主线程执行。

  事件的概念实际上并不是必须的,事件机制实际上就是异步过程的通知机制。我觉得它的存在是为了编程接口对开发者更友好。另一方面,所有的异步过程也都可以用事件来描述。例如:setTimeout可以看成对应一个时间到了!的事件。前文的setTimeout(fn, 1000);可以看成:

  timer.addEventListener('timeout', 1000, fn);

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; line-height: 19.0px; font: 13.0px "Helvetica Neue" }
p.p2 { margin: 0.0px 0.0px 0.0px 0.0px; line-height: 19.0px; font: 13.0px "Helvetica Neue"; min-height: 15.0px }

  六. 生产者与消费者

  从生产者与消费者的角度看,异步过程是这样的:工作线程是生产者,主线程是消费者(只有一个消费者)。工作线程执行异步任务,执行完成后把对应的回调函数封装成一条消息放到消息队列中;主线程不断地从消息队列中取消息并执行,当消息队列空时主线程阻塞,直到消息队列再次非空。

  

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; line-height: 19.0px; font: 13.0px "Helvetica Neue" }
p.p2 { margin: 0.0px 0.0px 0.0px 0.0px; line-height: 19.0px; font: 13.0px "Helvetica Neue"; min-height: 15.0px }

  七. 总结一下

  最后再用一个生活中的例子总结一下同步和异步:在公路上,汽车一辆接一辆,有条不紊的运行。这时,有一辆车坏掉了。假如它停在原地进行修理,那么后面的车就会被堵住没法行驶,交通就乱套了。幸好旁边有应急车道,可以把故障车辆推到应急车道修理,而正常的车流不会受到任何影响。等车修好了,再从应急车道回到正常车道即可。唯一的影响就是,应急车道用多了,原来的车辆之间的顺序会有点乱。

  

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; line-height: 19.0px; font: 13.0px "Helvetica Neue" }

  这就是同步和异步的区别。同步可以保证顺序一致,但是容易导致阻塞;异步可以解决阻塞问题,但是会改变顺序性。改变顺序性其实也没有什么大不了的,只不过让程序变得稍微难理解了一些。

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; line-height: 19.0px; font: 13.0px "Helvetica Neue" }
p.p2 { margin: 0.0px 0.0px 0.0px 0.0px; line-height: 19.0px; font: 13.0px "Helvetica Neue"; min-height: 15.0px }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; line-height: 19.0px; font: 13.0px "Helvetica Neue" }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; line-height: 19.0px; font: 13.0px "Helvetica Neue" }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; line-height: 19.0px; font: 13.0px "Helvetica Neue" }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; line-height: 19.0px; font: 13.0px "Helvetica Neue" }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; line-height: 19.0px; font: 13.0px "Helvetica Neue" }

让你高效的理解JavaScript中的同步、异步和事件循环的更多相关文章

  1. 详解JavaScript中的Event Loop(事件循环)机制

    前言 我们都知道,javascript从诞生之日起就是一门单线程的非阻塞的脚本语言.这是由其最初的用途来决定的:与浏览器交互. 单线程意味着,javascript代码在执行的任何时候,都只有一个主线程 ...

  2. 如何理解javascript中的同步和异步

    javascript语言是一门“单线程”的语言,不像java语言,类继承Thread再来个thread.start就可以开辟一个线程,所以,javascript就像一条流水线,仅仅是一条流水线而已,要 ...

  3. 理解JavaScript中的原型继承(2)

    两年前在我学习JavaScript的时候我就写过两篇关于原型继承的博客: 理解JavaScript中原型继承 JavaScript中的原型继承 这两篇博客讲的都是原型的使用,其中一篇还有我学习时的错误 ...

  4. 深入理解JavaScript中创建对象模式的演变(原型)

    深入理解JavaScript中创建对象模式的演变(原型) 创建对象的模式多种多样,但是各种模式又有怎样的利弊呢?有没有一种最为完美的模式呢?下面我将就以下几个方面来分析创建对象的几种模式: Objec ...

  5. 深入理解JavaScript中的属性和特性

    深入理解JavaScript中的属性和特性 JavaScript中属性和特性是完全不同的两个概念,这里我将根据自己所学,来深入理解JavaScript中的属性和特性. 主要内容如下: 理解JavaSc ...

  6. 深入理解javascript中执行环境(作用域)与作用域链

    深入理解javascript中执行环境(作用域)与作用域链 相信很多初学者对与javascript中的执行环境与作用域链不能很好的理解,这里,我会按照自己的理解同大家一起分享. 一般情况下,我们把执行 ...

  7. 【干货理解】理解javascript中实现MVC的原理

    理解javascript中的MVC MVC模式是软件工程中一种软件架构模式,一般把软件模式分为三部分,模型(Model)+视图(View)+控制器(Controller); 模型:模型用于封装与应用程 ...

  8. 理解javascript中的策略模式

    理解javascript中的策略模式 策略模式的定义是:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换. 使用策略模式的优点如下: 优点:1. 策略模式利用组合,委托等技术和思想,有效 ...

  9. 深入理解javascript中的立即执行函数(function(){…})()

    投稿:junjie 字体:[增加 减小] 类型:转载 时间:2014-06-12 我要评论 这篇文章主要介绍了深入理解javascript中的立即执行函数,立即执行函数也叫立即调用函数,通常它的写法是 ...

随机推荐

  1. 初识Redux-Saga

    Redus-saga是一个redux的中间件,主要用来简便而优雅的处理redux应用里的副作用(side effect相对于pure function这类概念而言的).它之所以可以做到这一点主要是使用 ...

  2. pt-tcp-model

    http://blog.9minutesnooze.com/analyzing-http-traffic-tcpdump-perconas-pttcpmodel/ #获取200k个packets tc ...

  3. Liunx vi编辑器一些指令

    最近几天学习了Liunx vi编辑器 的使用,感觉还比较容易.总结的一点心得: vi分为3个模式,命令模式,尾行模式,编辑模式. 1. 命令模式 与 编辑模式切换 a:光标向后移动一位进入编辑模式 i ...

  4. CTF 文件包含与伪协议

    正巧在写代码审计的文章,无意间看到了一篇CTF的代码审计,CTF题目很好,用的姿势正如标题,文件包含和伪协议. 先放出原文链接(http://www.freebuf.com/column/150028 ...

  5. C#常见错误解决方法

    1.能提供Visual Studio开发工具包吗? 解决方法: Visual Studio 2017开发环境下载地址: https://www.visualstudio.com/zh-hans/dow ...

  6. ptrdiff_t 和 size_t

    size_t和ptrdiff_t常常用来指示数组长度. size_t常用于表示数组的大小,可以一般的将他看为 typedef unsigned int size_t,实质是一个无符号整形.包含在头文件 ...

  7. 利用阿里云Centos7建站过程

    以下可能不尽详述,如有问题欢迎指出 准备过程:1. 阿里云主机一台2.域名一个 3.github个人帐号开始: 1.以root帐号登录云主机 2.安装apache [root@192 ~]# yum ...

  8. RGBA 和 opacity的区别

    两者都可以设置透明度 区别 RGBA 只影响当前元素 opacity 后代会继承该css 值,暂时还没有办法清除该css 在线演示

  9. PHP就业前景好不好一看便知,转行选择需谨慎!

    随着互联网行业迎来新一波的热潮,更多的年轻人选择软件行业发展.由于互联网本身快速发展.不断创新的特点,决定了只有以快开发速度和低成本,才能赢得胜利,才能始终保持网站的领先性和吸引更多的网民. 互联网的 ...

  10. CCF-201604-1-折点计数

    问题描述 试题编号: 201604-1 试题名称: 折点计数 时间限制: 1.0s 内存限制: 256.0MB 问题描述: 问题描述 给定n个整数表示一个商店连续n天的销售量.如果某天之前销售量在增长 ...