103 lines
5.6 KiB
Markdown
103 lines
5.6 KiB
Markdown
---
|
||
title: JavaScript异步操作
|
||
date: 2020-10-20
|
||
author: ac
|
||
tags:
|
||
- JavaScript
|
||
categories:
|
||
- Web
|
||
---
|
||
|
||
|
||
|
||
## JavaScript 异步操作
|
||
|
||
### 单线程模式
|
||
|
||
JavaScript只在一个线程上运行。即JavaScript同时只能执行一个任务。但不代表JavaScript引擎只有一个引擎。实际上,JavaScript引擎有多个线程,单个脚本只能在一个线程上运行(称为主线程)其他线程都是在后台配合。
|
||
|
||
JavaScript 语言的设计者意识到,这时 CPU 完全可以不管 IO 操作,挂起处于等待中的任务,先运行排在后面的任务。等到 IO 操作返回了结果,再回过头,把挂起的任务继续执行下去。这种机制就是 JavaScript 内部采用的“事件循环”机制(Event Loop)。
|
||
|
||
为了利用多核 CPU 的计算能力,HTML5 提出 Web Worker 标准,允许 JavaScript 脚本创建多个线程,但是子线程完全受主线程控制,且不得操作 DOM。所以,这个新标准并没有改变 JavaScript 单线程的本质。
|
||
|
||
### 同步任务和异步任务
|
||
|
||
- 同步任务:是那些没有被引擎挂起、在主线程上排队执行的任务。只有前一个任务执行完毕,才能执行后一个任务。
|
||
|
||
- 异步任务:是那些被引擎放在一边,不进入主线程、而进入【任务队列】的任务。只有引擎认为某个异步任务可以执行了(比如 Ajax 操作从服务器得到了结果),该任务(采用回调函数的形式)才会进入主线程执行。
|
||
|
||
|
||
### 任务队列和事件循环
|
||
|
||
JavaScript 运行时,除了一个正在运行的主线程,引擎还提供一个任务队列(task queue),里面是各种需要当前程序处理的异步任务。
|
||
|
||
> 实际上,根据异步任务的类型,存在多个任务队列。为了方便理解,这里假设只存在一个队列。
|
||
|
||
异步任务的写法通常是回调函数。一旦异步任务重新进入主线程,就会执行对应的回调函数。如果一个异步任务没有回调函数,就不会进入任务队列。
|
||
|
||
**事件循环**
|
||
JavaScript 引擎怎么知道异步任务有没有结果,能不能进入主线程呢?答案就是引擎在不停地检查,一遍又一遍,只要同步任务执行完了,引擎就会去检查那些挂起来的异步任务,是不是可以进入主线程了。这种循环检查的机制,就叫做事件循环(Event Loop)。
|
||
|
||
#### 定时器
|
||
|
||
JS中有两个定时器:setTimeout(func|code, delay)和setInterval(func|code, delay)
|
||
|
||
setTimeout函数用来指定某个函数或某段代码,在多少毫秒之后执行。如果推迟执行的是函数,就直接将函数名,作为setTimeout的参数。它返回一个整数,表示定时器的编号,以后可以用来取消这个定时器。
|
||
|
||
```javascript
|
||
var timerId = setTimeout(func|code, delay);
|
||
clearTimeout(timerId);
|
||
```
|
||
|
||
> 注意:当回调函数中存在this时,this指向window
|
||
|
||
setInterval函数的用法与setTimeout完全一致。区别在于setInterval指定某个任务每隔一段时间就执行一次,也就是无限次的定时执行。
|
||
|
||
**运行机制**
|
||
setTimeout和setInterval的运行机制,是将指定的代码移出本轮事件循环,等到下一轮事件循环,再检查是否到了指定时间。如果到了,就执行对应的代码,如果不到,就继续等待。
|
||
|
||
**存在问题**
|
||
setTimeout和setInterval指定的回调函数,必须等到本轮事件循环的所有同步任务都执行完,才会开始执行。由于前面的任务到底需要多少时间执行完,是不确定的,所以没有办法保证setTimeout和setInterval指定的任务一定会按照预定时间执行。
|
||
|
||
|
||
|
||
### 异步操作的模式
|
||
|
||
1. 回调函数
|
||
2. 事件监听
|
||
3. 发布/订阅
|
||
|
||
#### 回调函数
|
||
|
||
- 优点:简单、容易理解和实现;
|
||
- 缺点:不利于代码的阅读和维护,各个部分之间高度耦合(coupling),使得程序结构混乱、流程难以追踪(尤其是多个回调函数嵌套的情况),而且每个任务只能指定一个回调函数。
|
||
|
||
#### 事件监听
|
||
|
||
异步任务的执行不取决于代码的顺序,而取决于某个事件是否发生。所以通过绑定事件来决定函数调用顺序
|
||
|
||
- 优点:优点是比较容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,而且可以“去耦合”(decoupling),有利于实现模块化。
|
||
|
||
- 缺点:整个程序都要变成事件驱动型,运行流程会变得很不清晰。阅读代码的时候,很难看出主流程。
|
||
|
||
#### 发布/订阅
|
||
|
||
事件完全可以理解成“信号”,如果存在一个“信号中心”,某个任务执行完成,就向信号中心“发布”(publish)一个信号,其他任务可以向信号中心“订阅”(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做”发布/订阅模式”(publish-subscribe pattern),又称“观察者模式”(observer pattern)。
|
||
|
||
|
||
|
||
### 异步操作的流程控制
|
||
|
||
当存在多个异步操作时,如何确定异步操作执行的顺序,以及如何保证准守这种顺序
|
||
|
||
- 串行执行:一个个任务依次执行。当一个任务完成之后,再去执行另一个。
|
||
- 并行执行:即所有异步任务同时执行,等到全部完成以后,才执行final函数。效率比1要高,但问题是,如果并行的任务较多,很容易耗尽系统资源,拖慢运行速度。
|
||
- 串行与并行结合:就是设置一个门槛,每次最多只能并行执行n个异步任务,这样就避免了过分占用系统资源。
|
||
|
||
|
||
|
||
### 参考文章
|
||
|
||
[1] 步操作概述 https://wangdoc.com/javascript/async/general.html
|
||
|