1. 单线程模型
JavaScript只在一个线程上运行,同时只能执行一个任务,其他任务都必须在后面排队等待。虽然允许JS创建多个线程,但子线程完全受主线程控制,且不得操作 DOM,所以并不影响JS单线程的本质。
2. 任务队列和事件循环
2.1 任务队列
JavaScript 运行时,除了一个正在运行的主线程,引擎还提供一个任务队列(task queue),里面是各种需要当前程序处理的异步任务。(可能存在多个任务队列)
2.2 执行流程
1. 执行所有同步任务。
2. 同步执行完后,检查任务队列里面的异步任务,如果满足条件,那么异步任务就重新进入主线程开始执行,这时它就变成同步任务了。
3. 等到执行完,下一个异步任务再进入主线程开始执行。一旦任务队列清空,程序就结束执行。
2.3 异步实现方法
异步任务的写法通常是回调函数。一旦异步任务重新进入主线程,就会执行对应的回调函数。如果一个异步任务没有回调函数,就不会进入任务队列,也就是说,不会重新进入主线程,因为没有用回调函数指定下一步的操作。
2.4 事件循环
JavaScript 引擎怎么知道异步任务有没有结果,能不能进入主线程呢?答案就是引擎在不停地检查,一遍又一遍,只要同步任务执行完了,引擎就会去检查那些挂起来的异步任务,是不是可以进入主线程了。
3. 异步操作的模式
function f1() {
// ...
}
function f2() {
// ...
}
f1();
f2();
如何才能让f1执行完在执行f2呢?解决方案是,改写f1,把f2写成f1的回调函数。
function f1(callback) {
// ...
callback();
}
function f2() {
// ...
}
f1(f2);
4. 事件监听
采用事件驱动模式。异步任务的执行不取决于代码的顺序,而取决于某个事件是否发生。
还是以f1和f2为例。首先,为f1绑定一个事件(这里采用的 jQuery 的写法)。
f1.on('done', f2);
上面这行代码的意思是,当f1发生done事件,就执行f2。然后,对f1进行改写:
function f1() {
setTimeout(function () {
// ...
f1.trigger('done');
}, 1000);
}
上面代码中,f1.trigger(‘done’)表示,执行完成后,立即触发done事件,从而开始执行f2。
4.3 发布/订阅(观察者模式)
事件完全可以理解成“信号”,如果存在一个“信号中心”,某个任务执行完成,就向信号中心“发布”(publish)一个信号,其他任务可以向信号中心“订阅”(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做”发布/订阅模式”
首先,f2向信号中心jQuery订阅done信号
jQuery.subscribe('done', f2);
// 对f1的改写
function f1() {
setTimeout(function () {
// ...
jQuery.publish('done');
}, 1000);
}
上面代码中,jQuery.publish(‘done’)的意思是,f1执行完成后,向信号中心jQuery发布done信号,从而引发f2的执行。
f2完成执行后,可以取消订阅(unsubscribe)
jQuery.unsubscribe('done', f2);
此方法类似于事件监听,但优于事件监听,可查看有多少信号和订阅者。