同步即是一个任务必须等另一个任务执行完成才能开始执行,即排队等待执行。很容易出现浏览器卡死的现象。
异步即是一个任务不必等另一个任务执行完成即可执行。以ajax为例:
var option = { data:'', method: 'GET', url: 'http://p0.ifengimg.com/a/2017_21/98bbcc3961c083b.jpg', success: function(){ console.log('ajax-success'); }, fail: function(){ console.log('ajax-fail'); } }; $.ajax(option); var j = 0; for(var i=0;i<1000000000;i++){ if(i===99999999){ console.log(j); } else{ j++; } }
结果如下::
析:
代码是按顺序执行的,ajax操作在for循环前面,则应该先执行完ajax再执行for循环。
但是由于ajax是异步的,所以for循环不必等ajax执行完就开始执行了。
JavaScript是单线程的
原因:JavaScript设计目的是操作DOM。浏览器简单操作的用途(只能一个线程操作DOM)
但是为什么JavaScript能异步执行ajax或者定时任务呢?
因为浏览器不是单线程的,浏览器提供了其他线程来助JavaScript实现异步操作
那么同步任务与异步调用是如何执行的呢?
执行由js进行,那么应该是同步的。到底应该如何执行呢?
下面我们用代码来验证下:
<button id="test">点我</button> <script> $('#test').on('click', function(){ console.log('click'); }); var option = { data:'', method: 'GET', url: 'http://p0.ifengimg.com/a/2017_21/98bbcc3961c083b.jpg', success: function(){ console.log('ajax-success'); }, fail: function(){ console.log('ajax-fail'); } }; $.ajax(option); for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } for(var i=0;i<100000000;i++){ if(i===99999999){ console.log('j'); } } </script>
上述代码我们写了两个异步操作:点击以及ajax。
定义完成两个异步操作之后,又定义了一些循环的同步操作。
操作:执行代码过程中,一直点击按钮。
下面我们查看上述代码在浏览器中执行结果:
析:
同步任务的执行阻碍了点击事件回调的执行,点击事件阻碍了ajax回调的执行。
整个执行过程类似于:
同步任务执行完成—->异步任务同步执行
下面我们分析一下JavaScript异步机制。
一张图:
左侧stack:同步任务执行栈,右侧heap:堆,对象等定义。下面queue:任务队列
执行顺序:
一个JavaScript的单线程用来执行左侧栈中的同步任务,当所有同步任务执行完成之后,栈被清空,然后读取任务队列中一个待处理任务,并把相应的回调函数压入栈中,JavaScript单线程开始执行新的同步任务,执行完毕,栈被清空,继续读取任务队列中一个待处理任务,如此循环。
单线程每次从任务队列中读取任务都是不断循环的,每次栈被清空后,都会在任务队列中读取新的任务,如果没有新的任务就会等待,直到有新的任务进来,这就叫任务循环,因为每个任务都由一个事件所触发,所以也叫事件循环。
那么浏览器是如何渲染DOM的呢?
浏览器在任何情况下都至少有3个常驻线程:JavaScript线程、GUI线程、浏览器事件触发线程。
(a).javascript引擎线程
JavaScript引擎基于事件驱动单线程执行,一直等待着任务队列中任务到来,然后加以处理,浏览器无论什么时候只有一个JavaScript线程在运行JavaScript程序。
(b).GUI渲染线程
用来渲染浏览器界面,当界面需要重绘或者其他操作时,该线程就会执行。
注意:GUI渲染线程与JavaScript引擎线程互斥,当JavaScript引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到JavaScript引擎空闲时立即被执行。
(c).事件触发线程
当一个事件被触发时,该线程会把事件添加到待处理队列的队尾,等待JavaScript引擎的处理。
下面用一个生活中常见的例子来解释下整个过程:
早晨起床,小明穿上衣服之后打开微波炉热面包,然后他去洗漱了,听到微波炉叮的一声,小明去把面包拿出来吃早饭。
这个过程我们来分析下,整个过程只有一个人:小明,单线程在处理事情,对他来说所有的事情都是同步的。
这个过程中热面包是一个异步操作,因为小明在微波炉热面包的同时去洗漱了–>相当于注册了一个异步热面包的事件
听到微波炉叮的一声,小明拿出面包吃早饭–>叮的一声相当于一个触发,触发事件执行。叮是由浏览器事件触发线程执行的。
下面两个例子来验证你对异步的理解吧
for(var i = 0; i < 5; i++){ setTimeout(function(){ console.log(i); // this指向window }, 0); }
结果:5个5
析:
setTimeout是异步执行,浏览器会另起线程计时。
for循环是同步操作,同步任务执行完成才会执行异步操作。所以for循环结束,注册的几个回调函数才会执行。
所以结果是5个5
那么如何保留变量呢?
for(j=0; j<5; j++){ (function(j){ setTimeout(function(){ console.log(j); // this指向window }, 0); })(j); }
结果依次输出0 1 2 3 4
闭包来保留作用域。
参考文章:
JavaScript单线程和异步机制