单线程JavaScript实现异步过程详解


Posted in Javascript onMay 19, 2020

前两天硬着头皮在部门内部做了一次技术分享,主题如题。索性整理成文章留个纪念!

要了解异步实现,首先我们得先了解:

同步 & 异步

同步:会逐行执行代码,会对后续代码造成阻塞,直至代码接收到预期的结果之后,才会继续向下执行任务。

异步:调用之后先不管结果,继续向下执行任务。

网上各种文章对同步和异步的解释也不外如是,但是看文字总是有点晦涩难懂!我就生活化的来比拟一下这两个概念吧!

就好比请人吃饭:

比如你要请两个人吃饭,一个是巴菲特,由于他是举世瞩目股神想请他吃饭的人从这里排到了法国,你为表诚意,你会精心打扮自己,然后租一架飞机亲自去美国,请他跟你吃顿特色菜...那么为了请他吃个烤腰子,你全程都在为些事费心费力,投入大量的精力!

所以,也就阻塞了你干别的事情,是的,这就是同步!

请人吃顿饭就这么难吗?当然,也没有那么难!不信,你请我吃饭试试:

如果你想请我吃饭,那你只需要打个电话通知我一声:喂,今天晚上请你吃个海底捞啊!我:好啊!然后你不要来接我,到了点我自己去了!期间,你该干嘛就去干嘛!

看,其他也很简单嘛?瞧,这就是异步!

那么回到代码层面:

同步代码:(代码片段1)

function someTime() {
  let s = Date.now();
  while(true) {
    if (Date.now() - s > 2000) {
      console.log(2)
      break;
    }
  }
}
console.log(1);
someTime();
console.log(3);
// 其打印顺序:1 ...(2秒以后)... 2 3

异步代码:(代码片段2)

function someTime() {
  setTimeout(() => {
    console.log(2);
  }, 2000)
}

console.log(1);
someTime();
console.log(3);

// 其打印顺序:1 3 ...(2秒以后)... 2

看看,同步代码,当执行这种耗时操作时,就会停在原地,一定要等待这时间过去之后才会执行后面的代码!而异步代码,后面的执行完全不受影响...

JavaScript单线程

众所周知JavaScript是单线程的,所谓单线程是指程序执行时,所走的程序路径按照连续顺序排下来,前面的必须处理好,后面的才会执行!这个解释跟【同步】的解释如出一辙!

如此看起来异步编程对于单线程而言似乎并非正统,甚至有点矛盾。然而,通过刚才的例子,我们发现,JavaScript是真的实现了异步编程的!为啥加了个setTimeout()不能不阻塞了呢?按单线程的执行的话那如下代码会是怎么样的呢?

function timeOut() {
  setTimeout(() => {
    console.log('timeOut');
  }, 0)
}
function someTime() {
  let s = Date.now();
  while(true) {
    if (Date.now() - s > 2000) {
      console.log('some Time')
      break;
    }
  }
}
console.log(1);
timeOut();
someTime();
console.log(3);

如果是以单线程那种解释来执行的话,这个打印顺序应该是:1 - time Out - some Time - 3才对!然而,其真正的执行结果却是:1 - some Time - 3 - time Out

为什么?浏览器的多线程

JavaScript是脚本语言,它需要在一个宿主环境里才能运行,显然我们接触较多的宿主环境就是--浏览器!虽说JavaScript是单线程的,然而浏览器却不是!

单线程JavaScript实现异步过程详解

如图所求,JavaScript引擎线程称为主线程,它负责解析JavaScript代码;其他可以称为辅助线程,这些辅助线程便是JavaScript实现异步的关键了!

如(代码片段2):主线程负责自上而下顺序执行,当遇到setTimeout函数后,便将其交给定时器线程去执行,自己继续执行下面的代码!从而达到异步的目的。

不仅如此,更关键的是:

单线程JavaScript实现异步过程详解

任务队列

当定时器线程计时执行完之后,会将回调函数放入任务队列中!

当这些任务加入到任务队列后并不会立即执行,而是处于等候状态!等主线程处理完了自己的事情后,才来执行任务队列中任务!

这个过程我感觉像是古代嫔妃被翻了牌子后,就需要在自己寝宫里精心准备,等待皇上批完凑折后的驾临...(哦,别想歪了!)

宏任务 & 微任务

然而,异步任务却又分为两种:一种叫“宏任务”(MacroTask 或者 Task),一种叫“微任务”(MicroTask)!

这又是两个啥玩意呢?

单线程JavaScript实现异步过程详解

光看这个依然晦涩难懂,那我们来看一段代码吧!

console.log(1);
setTimeout(() => {
  console.log(2);
}, 0);
Promise.resolve().then(() => {
  console.log(3);
});
console.log(4);

这段代码的执行结果:1 - 4 - 3 - 2。LOOK!2是最后打印的,哪怕该计时器的时间设置为0。通过之前的同步和异步的解释,1和4先于2打印应该很好理解了,但同样是异步,3也优先于2打印,这又是为什么呢?答案就是因为 setTimeout属于宏任务,而Promise属于微任务!

好吧~ 这就是宏任务和微任务的差别...什么?没懂?

微任务是皇后所生的,是嫡子;而宏任务是某个小妃子所生, 是庶子!你说选太子的时候谁优先?

浏览器的Event Loop

1.执行全局Script同步代码,形成一个执行栈;

2.在执行代码时当遇到如上异步任务时便会按上文所描述的将宏任务回调加入宏任务队列,微任务回调加入微任务队列;

3.然而,回调函数放入任务队列后也不是立即执行;会等待执行栈中的同步任务全部执行完清空了栈后引擎才能会去任务队列检查是否有任务,如果有那便会将这些任务加入执行栈,然后执行!

4.执行栈清空后,会先去检查微任务队列是否有任务,逐一将其任务加入执行栈中执行,期间如果又产生了微任务那继续将其加入到列队末尾,并在本周期内执行完,直到微任务队列的任务全部 清空,执行栈也清空后,再去检查宏任务队列是否有任务,取到队列队头的任务放入到执行栈中执行,其他可能又会产生微任务,那当本次执行栈中的任务结果清空后又会去检查微任务队列...

5.引擎会循环执行如上步骤,这就是Event Loop!

单线程JavaScript实现异步过程详解

又要上代码了:

console.log('start');
setTimeout(() => {
  console.log('time1');
  Pormise.resolve().then(() => {
    console.log('promise1');
  })
}, 0);
setTimeout(() => {
  console.log('time2');
  Pormise.resolve().then(() => {
    console.log('promise2');
  })
}, 0);
Pormise.resolve().then(() => {
  console.log('promise3');
});
console.log('end');

这段代码的打印顺序:

start - end - promise3 - timer1 - promise1 - timer2 - promise2

据说:node 10.x版本上面的输入结果会是:

start - end - promise3 - timer1 - timer2 - promise1 - promise2

node 11.x版本以后改了,输出跟浏览器输出一致了!

Web Worker

HTML5中支持了Web Worker,使得能够同时执行两段JS了,那是不是就是说JS实现了“多线程”了呢?我们来看看Web Worker的官方解释:

通过使用Web Workers,Web应用程序可以在独立于主线程的后台线程中,运行一个脚本操作。这样做的好处是可以在独立线程中执行费时的处理任务,从而允许主线程(通常是UI线程)不会因此被阻塞/放慢。

独立线程,看似像是实现了“多线程”,然而他是独立于主线程,也就是主线程依然是那个主线程没有变!虽然你大妈已经不是你大妈了,但是你大爷还是你大爷!JS单线程的本质依然没有变!

WebWorker是向浏览器申请一个子线程,该子线程服务于主线程,完全受主线程控制。

Web Worker注意事项:

单线程JavaScript实现异步过程详解

写了一个demo:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Web Worker</title>
</head>
<body>
  <button onclick="startWorker()">开始</button>
  <button onclick="stopWorker()">停止</button>
  <button onclick="updateNum()">在运行时点击</button>
  <div id="output"></div>
  <div id="num"></div>

  <script id="worker" type="app/worker">
    function updateSync() {
      for (let i = 0; i < 10000000000; i++) {
        if (i % 100000 === 0) {
          postMessage(i);
        }
      }
    }
    updateSync();
  </script>

  <script>
    let worker;
    function startWorker() {
      let blob = new Blob([document.querySelector('#worker').textContent]);
      let url = window.URL.createObjectURL(blob);
      console.log(url);
      worker = new Worker(url);

      worker.onmessage = function(e) {
        document.getElementById('output').innerHTML = e.data;
      }
    }

    function stopWorker() {
      if (worker) {
        worker.terminate();
      }
    }
    
    let num = 0;
    function updateNum() {
      num++;
      document.getElementById('num').innerHTML = num;
    }
  </script>
</body>
</html>

这段代码可以稍微解释一下Web Worker的用途之一 --执行费时的处理任务吧!

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
Prototype 学习 工具函数学习($A方法)
Jul 12 Javascript
js写一个字符串转成驼峰的实例
Jun 21 Javascript
用Jquery.load载入页面后样式没了页面混乱的解决方法
Oct 20 Javascript
node.js中的http.request方法使用说明
Dec 14 Javascript
AngularJS基础知识笔记之表格
May 10 Javascript
jQuery时间轴插件使用详解
Jul 16 Javascript
AngularJS 视图详解及示例代码
Aug 17 Javascript
基于jQuery实现瀑布流页面
Apr 11 jQuery
利用node.js实现自动生成前端项目组件的方法详解
Jul 12 Javascript
JS图片延迟加载插件LazyImgv1.0用法分析【附demo源码下载】
Sep 04 Javascript
vuex中使用对象展开运算符的示例
Sep 25 Javascript
JavaScript实现重力下落与弹性效果的方法分析
Dec 20 Javascript
vue相关配置文件详解及多环境配置详细步骤
May 19 #Javascript
JS使用for in有序获取对象数据
May 19 #Javascript
基于js实现数组相邻元素上移下移
May 19 #Javascript
Node.js API详解之 zlib模块用法分析
May 19 #Javascript
微信jssdk踩坑之签名错误invalid signature
May 19 #Javascript
JavaScript实现简单的弹窗效果
May 19 #Javascript
js实现自定义右键菜单
May 18 #Javascript
You might like
php miniBB中文乱码问题解决方法
2008/11/25 PHP
PHP验证码类代码( 最新修改,完全定制化! )
2010/12/02 PHP
解析PHP中数组元素升序、降序以及重新排序的函数
2013/06/20 PHP
php实现设计模式中的单例模式详解
2014/10/11 PHP
PHP实现留言板功能的详细代码
2017/03/25 PHP
jQuery中delegate与on的用法与区别示例介绍
2013/12/20 Javascript
JS中判断JSON数据是否存在某字段的方法
2014/03/07 Javascript
jQuery.holdReady()使用方法
2014/05/20 Javascript
jQuery Validate验证框架经典大全
2015/09/23 Javascript
JavaScript统计字符串中每个字符出现次数完整实例
2016/01/28 Javascript
Bootstrap编写一个兼容主流浏览器的受众门户式风格页面
2016/07/01 Javascript
JQuery 设置checkbox值二次无效的解决方法
2016/07/22 Javascript
老生常谈JavaScript 正则表达式语法
2016/08/20 Javascript
JavaScript实战(原生range和自定义特效)简单实例
2016/08/21 Javascript
BootStrap表单验证 FormValidation 调整反馈图标位置的实例代码
2017/05/17 Javascript
详解AngularJS 过滤器的使用
2018/06/02 Javascript
IE11下处理Promise及Vue的单项数据流问题
2019/07/24 Javascript
[46:59]完美世界DOTA2联赛PWL S2 GXR vs Ink 第二场 11.19
2020/11/20 DOTA
Web服务器框架 Tornado简介
2014/07/16 Python
使用Python的Twisted框架编写非阻塞程序的代码示例
2016/05/25 Python
Python数据结构之双向链表的定义与使用方法示例
2018/01/16 Python
Djang的model创建的字段和参数详解
2019/07/27 Python
zooplus波兰:在线宠物店
2019/07/21 全球购物
法国最大的在线眼镜店:EasyLunettes
2019/08/26 全球购物
业务助理岗位职责
2013/11/18 职场文书
优秀女职工事迹材料
2014/02/06 职场文书
交通事故赔偿协议书范本
2014/04/15 职场文书
高考标语大全
2014/06/05 职场文书
管理工程专业求职信
2014/08/10 职场文书
公司会议开幕词
2015/01/29 职场文书
销售经理岗位职责
2015/01/31 职场文书
篮球赛闭幕式主持词
2015/07/03 职场文书
考试后的感想
2015/08/07 职场文书
成人成长感言如何写?
2019/08/16 职场文书
在Python中如何使用yield
2021/06/07 Python
CentOS MySql8 远程连接实战
2022/04/19 MySQL