单线程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 相关文章推荐
js图片向右一张张滚动效果实例代码
Nov 23 Javascript
利用jquery动画特效和css打造的侧边弹出垂直导航
Apr 04 Javascript
jQuery实现仿QQ头像闪烁效果的文字闪动提示代码
Nov 03 Javascript
学习JavaScript设计模式(策略模式)
Nov 26 Javascript
JS添加删除DIV的简单实例
Jul 08 Javascript
微信公众平台开发教程(五)详解自定义菜单
Dec 02 Javascript
详解jQuery停止动画——stop()方法的使用
Dec 14 Javascript
详解AngularJS用Interceptors来统一处理HTTP请求和响应
Jun 08 Javascript
React教程之Props验证的具体用法(Props Validation)
Sep 04 Javascript
详解Webpack实战之构建 Electron 应用
Dec 25 Javascript
Vue中v-for的数据分组实例
Mar 07 Javascript
Vue拖拽组件列表实现动态页面配置功能
Jun 17 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 $_SERVER windows系统与linux系统下的区别说明
2014/02/14 PHP
PHP include任意文件或URL介绍
2014/04/29 PHP
仿dedecms下拉分页样式修改的thinkphp分页类实例
2014/10/30 PHP
Symfony2 session用法实例分析
2016/02/04 PHP
php curl批处理实现可控并发异步操作示例
2018/05/09 PHP
PHP7 其他修改
2021/03/09 PHP
js鼠标点击事件在各个浏览器中的写法及Event对象属性介绍
2013/01/24 Javascript
jQuery层次选择器选择元素使用介绍
2013/04/18 Javascript
jQuery实现返回顶部效果的方法
2015/05/29 Javascript
轻松学习jQuery插件EasyUI EasyUI创建树形菜单
2015/11/30 Javascript
使用JQuery选择HTML遍历函数的方法
2016/09/17 Javascript
BootStrapValidator校验方式
2016/12/19 Javascript
使用nodejs下载风景壁纸
2017/02/05 NodeJs
react-native 封装选择弹出框示例(试用ios&amp;android)
2017/07/11 Javascript
BootstrapTable加载按钮功能实例代码详解
2017/09/22 Javascript
微信小程序实现多选删除列表数据功能示例
2019/01/15 Javascript
vue element-ui之怎么封装一个自己的组件的详解
2019/05/20 Javascript
JavaScript生成随机验证码代码实例
2019/09/28 Javascript
jQuery实现移动端扭蛋机抽奖
2020/11/08 jQuery
通过C++学习Python
2015/01/20 Python
Unicode和Python的中文处理
2017/03/19 Python
python2.7到3.x迁移指南
2018/02/01 Python
使用python实现BLAST
2018/02/12 Python
python matplotlib绘图,修改坐标轴刻度为文字的实例
2018/05/25 Python
在python中使用requests 模拟浏览器发送请求数据的方法
2018/12/26 Python
关于Python中的向量相加和numpy中的向量相加效率对比
2019/08/26 Python
基于Python下载网络图片方法汇总代码实例
2020/06/24 Python
详解使用Python写一个向数据库填充数据的小工具(推荐)
2020/09/11 Python
美国快时尚彩妆品牌:Winky Lux(透明花瓣润唇膏)
2018/11/06 全球购物
优秀大学生推荐信范文
2013/11/28 职场文书
小学生安全保证书
2014/02/01 职场文书
美术教学感言
2014/02/22 职场文书
合同意向书范本
2014/07/30 职场文书
客户答谢会致辞
2015/01/20 职场文书
学生乘坐校车安全责任书
2015/05/11 职场文书
公司仓库管理制度
2015/08/04 职场文书