javascript同步与异步深入理解

同步即是一个任务必须等另一个任务执行完成才能开始执行,即排队等待执行。很容易出现浏览器卡死的现象。
异步即是一个任务不必等另一个任务执行完成即可执行。以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++;
	    }
	}

结果如下::
01

析:
代码是按顺序执行的,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。
定义完成两个异步操作之后,又定义了一些循环的同步操作。
操作:执行代码过程中,一直点击按钮。
下面我们查看上述代码在浏览器中执行结果:
02
析:
同步任务的执行阻碍了点击事件回调的执行,点击事件阻碍了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单线程和异步机制

此条目发表在JavaScript分类目录。将固定链接加入收藏夹。

发表评论

电子邮件地址不会被公开。 必填项已用*标注