同步
同步
同步是代码从上到下依次执行,上一个任务结束后,才能执行下一个任务。
如下图所示,任务1执行完后,再执行任务2,任务2执行完后再执行任务3,依次类推...
同步优势
同步是任务有序进行,不会造成资源上处理上的混乱。
1.任务有序进行较好的处理了任务之间的依赖性,如后一个任务需要前一个任务的结果。
2.如果多个任务处理同一个资源,不会造成资源处理的混乱。
var a = 1;
function task1(){
console.log(a);
for(var i = 0; i <10000;i++){
a++;
}
console.log(a);
}
function task2(){
console.log(a);
for(var i = 0; i <10000;i++){
a--;
}
console.log(a);
}
task1();
task2();
task1、task2都操作变量a。先执行task1, 执行完 task1后得到一个a的结果值。然后task2处理task1处理的结果值。
如果task1与task2不是同步的,task1没有执行完,去执行task2,task2执行一会,再去执行task1,... ,可能a的值都不是task1、 task2想要的结果。
同步弊端
同步上从上到下依次执行的,必须等到上个任务完成,才能处理下一个任务。如果上一任务占用的时间比较长,会让下一个任务长时间等待。
异步
异步
1.任务不是按照顺序依次执行的。
2.不等到上个任务执行完,执行下个任务。
如果前一个任务没有执行完,下一个任务可能已经开始,下一个任务执行一段时间,又去执行上一个任务...,看起来像多个任务同时执行。
如下图所示,任务1执行一段时间后,去执行任务2,然后再执行任务1,再执行任务2,执行任务3。任务1与任务2不是按照顺序执行的,各自占用一段时间执行,好像任务1与任务2,同时执行的。
异步优势
异步不用等待上一个任务执行完,就可以执行下一个任务,不用较长时间等待上一任务完成。
异步弊端
如果使用异步 处理同一个资源,可能造成资源的混乱。如上面的task1、 task2共同处理a值。
js引擎使同步与异步协作
js引擎在解析javascript时,同时使用了同步与异步的思想。
同步
线程是CPU独立运行和独立调度的基本单位(可以理解为一个进程中执行的代码片段),进程是资源分配的基本单位(进程是一块包含了某些资源的内存区域)。浏览器中有多个线程协作共同完成前端界面的展示。
js引擎是单线程的,从上到下依次执行代码,依次处理任务。这样不会导致资源的混乱。浏览器是根据DOM树来显示页面元素的,如果一个任务处理DOM,另外一个任务也在处理DOM,那么就会造成处理DOM混乱。
如下图所示,任务A处理DOM一段时间,然后任务A停止处理DOM,任务B开始处理这个DOM,任务B处理DOM停止,任务A继续处理DOM。那么任务A接收的不是自己处理DOM的预期结果,而是任务B处理后的结果。
如下代码规规矩矩从上到依次执行,先执行A任务,A任务执行完了再执行B任务。保证资源的处理不会混乱。
var num = 1;
function A(){
num = 2;
}
function B(){
num = 3;
}
A();
B();
如果上一个任务执行时间比较长,导致下一个任务不能执行,导致浏览器卡顿怎么办?
如网络请求数据,网络请求数据的过程用的时间比较长,就需要一直等待网络资源的请求,直到请求完成。这样会让后面的执行等待很长时间。为此,浏览器使用了基于异步的事件驱动的处理。
异步
javascript引擎线程从上到下依次执行javascript代码(回调不执行)。回调被其他线程(事件触发线程、计数器触发线程)触发后,放入一个异步队列中。
即浏览器是把上一个任务未完成的部分(作为子任务)存起来,继续执行下面的任务当浏览器空闲时,再去处理未完成的部分。这样既能保证任务能够顺序执行,又不会因为上一个任务时间过长,导致下个任务需要长时间等待。
线程间执行是异步的,回调被触发时,可能javascript引擎线程正在从上到下执行代码,也可能已经执行完毕。
javascript引擎线程从上到下执行完代码后,开始查找异步队列中是否有任务,如有任务则按照队列的顺序依次执行任务。
$(document).ready(function(){ $('div').click(function(){ console.log('click'); }) }) setTimeout(function(){ console.log('timer'); },100); var count = 0; setInterval(function(){ count++; console.log('inter'+count); },1000);
打印结果:
图1 异步队列
100ms后,计时器触发线程触发回调,把回调放入队列中;每个1000ms周期,计时器触发线程触发回调,Iterval的回调放入队列中;点击div时,事件触发线程触发回调,事件的回调放到队列中。若引擎线程空闲,依次执行队列中的任务。
异步编程方式
异步编程方式:发布/订阅(事件触发、回调函数)、promise(ES6)、async函数(ES6)。
发布/订阅
浏览器实现
1.ajax
通过ajax请求网络资源,网络资源返回以后,就会触发预先写好的回调函数,并且把回调函数任务放入执行队列中,当该回调函数在队列第一个并且js引擎执行队列时,该回调函数执行。
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("get",url,true);
xmlhttp.send(null);
xmlhttp.onreadystatechange = callback;
function callback(){
if(xmlhttp.readySate == 4){
if(xmlhttp.status == 200){
//...
}
}
}
2.setTimeout、setInterval
setTimeout(callback,time)
该方法的含义是time时间后把callback放入异步队列,当该函数在队列头,并且js引擎执行该队列时,此回调函数执行。
setTimeout(function(){
var a = 1;
},0),
0毫秒后把回调函数放入异步队列。
3.addEventListener
elem.addEventListener('click',callback,false);
当点击elem元素时,把callback放到异步队列。
开发者实现
开发者自己通过发布/订阅 实现异步。
var taskQueue = [];
//订阅
function subscribe(task){
taskQueue.push(task);
}
//发布
function publish(){
for(var i = 0; i < taskQueue.length; i++){
taskQueue[i]();
}
}
subscribe(function(){console.log(1)});//订阅
publish();//发布
Promise
Promise是ES6定义的规范,需要浏览器去实现。
promise有3种状态:pendding, resolve, reject。
每个promise某一时刻只能有其中的一种状态,pending为初始状态,并且pendding只能一次转向resolve或reject状态。
可通过then方法注册将要执行的任务,这个任务什么时候执行,由promise的状态转换时间决定。
var promise = new Promise(function(resolve,reject){
setTimeout(resolve,100);//100ms后resolve放入异步队列
});
//promise由pending状态转为resolve状态,执行第2个参数的函数;
//由pending状态转为reject状态,执行的第2个参数的函数
promise.then(function resolve(){console.log(alert('resolve');},function reject(){alert('reject')});
async函数
async function print(value,time){
await timeout(time);//异步任务 当这个任务完成后,才能继续向下执行
console.log(value);
}
function timeout(time){
new Promise(function(resolve){
setTimeout(resolve,timeout);
}));
}
print('hi',100);
100ms后输出'hi'
综上:
(1)发布/订阅(事件触发、回调函数)、promise(ES6)思想是先注册一个任务,但不知道这个任务什么时候执行,需要合适的时间去执行这个任务。
(2)async函数管理异步任务的上下执行顺序,异步执行完后,继续执行后面的代码。