一、异步:现在与未来:
? 如何表达和控制持续一段时间的程序行为,是使用类似JS这样的语言编程时,很重要但常常被误解的一点。
? 持续一段时间,不是指类似于 for循环开始到结束的过程。而是指 程序的一部分现在运行,而另一部分则在未来运行。现在与将来之间有一段间隙,这段间隙在实际程序中,可以是等待用户输入、从数据库或文件系统中请求数据、通过网络发送数据并等待响应,或者是在以固定时间间隔执行重复任务(比如动画)。
? 管理这段间隙,就是异步编程的核心。本章将深入探讨异步的概念及其在JS中的运作模式。
一)、分块的程序:
? 无论JS程序是在多个JS文件或一个JS文件,JS中都是都是由一个个块组成。同一时间只有一个块可以在 现在执行。最常见的块就是 函数。
? 例如:
var data = ajax("http://some.url.1");
console.log(data); // data通常并不会出现Ajax的结果
? 因为标准Ajax请求不是同步完成的,当打印data时,ajax函数可能还没有返回任何值给变量data。
? 如何能够确定得到ajax函数的返回值?1、将ajax(...)
能够阻塞到响应返回,即发出ajax请求后什么事也不做,直到得到返回值。这与我们希望将一部分值在 未来运行是相违背的。
? 现在我们发出一个异步Ajax请求,然后在将来才能得到返回的结果。实现“等待”的最简单方法,是使用 回调函数。
ajax("http://some.url.1", function myCallbackFunction(data){
console.log(data); // 这里得到数据
})
? 另外,
function now() {
return 21;
}
function later() {
answer = answer * 2;
console.log("Meaning of life:", answer);
}
var answer = now();
setTimeout(later, 1000); // Meaning of life: 42
? 任何时候,只要把一段代码包装称一个函数,并指定它在响应某个时间(定时器、鼠标点击、Ajax响应等)时执行,你就在代码中创建了一个将来执行的块,也就在这个程序中引入了异步机制*。
- 不同的浏览器和JS环境可能有不同的控制台异步实现。
二)、事件循环:
? JS引擎并不是独立运行的,它运行在宿主环境中,大多数为Web浏览器,也有如Node.js等服务器端环境。
? 这些环境提供一种机制来处理程序中多个块的执行,且执行每块时调用JS引擎,这种机制被称为 事件循环。 可以按照下面的伪代码来理解事件循环:
var eventLoop = [];
var event;
//“永远”执行
while (true) {
// 一次tick
if (eventLoop.length > 0) {
event = eventLoop.shift();
try {
event();
}
catch (err) {
reportError(err);
}
}
}
? setTimeout(..)
无法将回调函数直接挂在事件循环队列中,只能通过定时器,让环境将回调函数放进去。因此setTimeout(..)
方法精度不是很高。
三)、并行线程:
? ”异步“与”并行“常常被混为一谈,但实际上它们的意义完全不同。
-
异步:关于
现在
和将来
的时间间隙。事件循环把自身的工作分成一个个任务并顺序执行,不允许对共享内存并行访问和修改。通过分立线程中彼此合作的事件循环,并行和顺序执行可以共存。
-
并行:关于能够同时发生的事情。
并行最常见的工具有
进程
与线程
。进程与线程独立运行,并可能同时运行,多个线程能够共享单个进程的内存。 -
JS不支持跨线程共享数据,并且具有
完整运行
特性,即如果有两个函数执行,两个函数不会交替运行,一定是先完整执行第一个函数,然后才是第二个函数。虽然JS不需要考虑 线程层次的不确定性,但是依然存在
竞态条件
,考虑下面的代码:
var a = 1;
var b = 2;
function foo() {
a++;
b = b * a;
a = b + 3;
}
function bar() {
b--;
a = 8 + b;
b = a * 2;
}
ajax("http;//some.url.1", foo);
ajax("http;//some.url.2", bar);
? 这一段代码可能有很多的输出,这种不确定性来自于 函数(事件)顺序
级别上,换句话说,这种不确定性低于多线程情况
。
四)、并发:
? 设想一个状态更新列表,随着用户向下滚动列表而住就按加载更多内容。这里需要两个”进程“,一个监听页面滚动触发onscroll
,并发起Ajax请求,另一个接受Ajax请求,并把内容展示到页面。当用户滚动页面时,可能在等待第一个响应的同时,就会有第二、第三个请求发出。
? 两个或多个”进程“同时执行就出现了并发,不管组成它们的单个运算是否 并行执行(在独立的处理器或处理器核心上同时运行)。可以把并发看作”进程级的并行,与运算级的并行(不同处理器上的线程)相对。
- 如果并发的”进程“需要通过
作用域
或DOM
间接监护,就需要对交互进行协调,避免竞态的出现。
五)、任务:
? 在ES6中,有一个新的概念建立在事件循环队列之上,叫做任务队列(job queue)
,这个概念用于Promise的异步特性。
? 任务队列是挂在前文提到的事件循环队列的每个tick之后的一个队列,但是不是被添加到队列末尾,而可以直接插队,优先处理,也即:”尽可能早的将来“。
六)、语句顺序:
? 代码中语句的顺序和JS引擎执行语句的顺序并不一定要一致。JS引擎在编译代码之后,可能会对语句的顺序进行重新安排,以提高执行速度。可以保证的是,JS引擎在编译阶段执行的优化都是安全的优化。