正文
1、所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)
2、主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
3、一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
4、主线程不断重复上面的第三步。
常见的异步任务
onclick等事件 由浏览器内核的 DOM Binding 模块来处理,当事件触发的时候,回调函数会立即添加到任务队列中。
setTimeout 会由浏览器内核的 timer 模块来进行延时处理,当时间到达的时候,才会将回调函数添加到任务队列中
setTimeout(function(){
console.log('1');
},0);
console.log('2');
这段代码的输出为 2 1 ,因为setTimeout是一个异步的操作,当时间到达之后,回调函数会被添加到任务队列中。主线程中的所有任务结束之后,才会去参看任务队列中的任务,输出 1
ajax 则会由浏览器内核的 network 模块来处理,在网络请求完成返回之后,才将回调添加到任务队列中。
异步编程的四种方法
除了onclick等事件、ajax请求这些天生的异步任务,有一些函数本身很耗时,其他函数依赖这个函数的执行结果,这时我们就要手动进行异步编程了。
以下我们假设:两个函数f1和f2,f1很耗时,f2需要等待f1的执行结果。
回调函数
这是异步编程最基本的方法。
我们使用setTimeout函数将f1变成了异步操作,不会程序运行。f1运行结束后,再运行f2。
function f1(callback){
setTimeout(function () {
// f1的任务代码
callback();
}, 1000);
}
f1(f2);
回调函数的优点是简单、容易理解和部署,缺点是不利于代码的阅读和维护,各个部分之间高度耦合,流程会很混乱(不能直接看出f1和f2之间的依赖关系)
事件监听
任务的执行不取决于代码的顺序,而取决于某个事件是否发生
f1.trigger('done')表示,执行完成后,立即触发done事件,从而开始执行f2。