单线程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 相关文章推荐
input 高级限制级用法
Mar 26 Javascript
网页编辑器ckeditor和ckfinder配置步骤分享
May 24 Javascript
加载远程图片时,经常因为缓存而得不到更新的解决方法(分享)
Jun 26 Javascript
使用js画图之饼图
Jan 12 Javascript
jQuery超赞的评分插件(8款)
Aug 20 Javascript
JQuery实现简单的服务器轮询效果实例
Mar 31 Javascript
jQuery选择器之子元素选择器详解
Sep 18 jQuery
Vue.js 表单控件操作小结
Mar 29 Javascript
js实现同一个页面,多个enter事件绑定的示例
Oct 10 Javascript
微信小程序自定义toast的实现代码
Nov 16 Javascript
详解webpack4.x之搭建前端开发环境
Mar 28 Javascript
vue $mount 和 el的区别说明
Sep 11 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
phpmyadmin安装时提示:Warning: require_once(./libraries/common.inc.php)错误解决办法
2011/08/18 PHP
浅析PHP安装扩展mcrypt以及相关依赖项(PHP安装PECL扩展的方法)
2013/07/05 PHP
php中如何防止表单的重复提交
2013/08/02 PHP
收藏一些不常用,但是有用的代码
2007/03/12 Javascript
(JS实现)MapBar中坐标的加密和解密的脚本
2007/05/16 Javascript
JS文本框追加多个下拉框的值的简单实例
2013/07/12 Javascript
JS+CSS实现的经典圆角下拉菜单效果代码
2015/10/21 Javascript
JavaScript无操作后屏保功能的实现方法
2017/07/04 Javascript
Vue全局分页组件的实现代码
2018/08/10 Javascript
vue中的watch监听数据变化及watch中各属性的详解
2018/09/11 Javascript
如何在基于vue-cli的项目自定义打包环境
2018/11/10 Javascript
webpack4 配置 ssr 环境遇到“document is not defined”
2019/10/24 Javascript
移动端JS实现拖拽两种方法解析
2020/10/12 Javascript
Vue3.0的优化总结
2020/10/16 Javascript
[04:01]2014DOTA2国际邀请赛 TITAN告别Ohaiyo期望明年再战
2014/07/15 DOTA
python去掉字符串中重复字符的方法
2014/02/27 Python
python3实现短网址和数字相互转换的方法
2015/04/28 Python
python 默认参数问题的陷阱
2016/02/29 Python
python机器学习库常用汇总
2017/11/15 Python
Python学习笔记之列表推导式实例分析
2019/08/13 Python
python画图常规设置方式
2020/03/05 Python
关于python 跨域处理方式详解
2020/03/28 Python
python中setuptools的作用是什么
2020/06/19 Python
python利用tkinter实现图片格式转换的示例
2020/09/28 Python
python爬虫中的url下载器用法详解
2020/11/30 Python
2014最新房贷收入证明范本
2014/09/12 职场文书
公安机关查摆剖析材料
2014/10/10 职场文书
《改造我们的学习》心得体会
2014/11/07 职场文书
2014年采购工作总结
2014/11/20 职场文书
参观邀请函范文
2015/02/02 职场文书
2015年行政人事部工作总结
2015/05/13 职场文书
立项申请报告范本
2015/05/15 职场文书
2019年销售部季度工作计划3篇
2019/10/09 职场文书
导游词之丽江普济寺
2019/10/22 职场文书
Python3 如何开启自带http服务
2021/05/18 Python
CSS 制作波浪效果的思路
2021/05/18 HTML / CSS