JavaScript中異步編程

一 關於事件的異步

事件是JavaScript中最重要的一個特徵,nodejs就是利用js這一異步而設計出來的。因此這裏講一下事件機制。php

在一個js文件中,若是要運行某一個函數,有2中手段,一個就是直接調用,好比foo(),第二就是利用事件來觸發,這中函數也叫回調函數,好比傳遞給setTimeout函數和onready屬性。css

1.setTimeout函數中的事件異步

setTimeout本質上也是一種異步事件,當延遲時間到的時候觸發該事件,可是有的有的時候(其實也是大部分時候)都不會按照給定的延遲時間執行,先看下面的代碼html

        var start = new Date();
        setTimeout(function() {
            console.log('settimeout1:',new Date()-start);
        }, 500);
        while (new Date() - start < 1000) {
            console.log('in while');
        }
        document.getElementById('test').addEventListener('click', function(){
            console.log('test:',new Date()-start);
        }, false)
        for(var i=0;i<10000;i++){
            console.log('in for');
        }
        setTimeout(function(){
            console.log('settimeout2: ',new Date()-start);
        },1000);
        /* 10214
        in while
        index.jsp (第 19 行)
        10000
        in for
        index.jsp (第 25 行)
        settimeout1: 2263
        index.jsp (第 16 行)
        settimeout2: 3239
        index.jsp (第 28 行)
        test: 10006
        index.jsp (第 22 行)
        test: 28175
        index.jsp (第 22 行)
        test: 28791
        index.jsp (第 22 行)
        test: 28966
        index.jsp (第 22 行) */

 

若是按照正常的理解,延遲函數應該在500毫秒以後打斷while循環,而事實上並無,而且,我在while循環和for循環期間點擊div時候並無當即輸出test,給出的解釋就是:node

a)事件隊列。調用setTimeout函數的時候,會把傳入它的回調函數加入到事件隊列中去(事件已經初始化而且在內存了),而後繼續執行後面的代碼,直到再也沒有代碼能夠運行(沒有正常的運行流了,不包括事件函數等異步的內容),就會從事件隊列裏面pop出一個合適的事件來運行。jquery

b)js是單線程的,事件處理器在線程空閒以前是不會運行的。ajax

2 普通事件的異步和setTimeout相似

二 promise對象和deferred對象

1. promise

promise是一種解決ajax等異步編程回調函數嵌套太多致使代碼晦澀難懂的解決方案,特別是在nodejs中,異步無處不在。不一樣的框架對promise的實現,一下是jquery中的promise的API。編程

這裏不講promise的實現原理,關於原理在另外的篇幅中介紹。json

傳統的ajax異步編程是這麼寫的(jquery1.5以前):promise

$.get('url', function(){
    $.get('url1', function(){
        $.get('url2', function(){

        }, 'json');
    }, 'json');
}, 'json');

 這麼寫代碼給開發和維護帶來了極大的困難,好在jquery1.5之後引入了promise,就能夠這麼寫了:瀏覽器

 $.ajax( "example.php" )
.done(function() { alert("success"); })
.fail(function() { alert("error"); })
.always(function() { alert("complete"); });

 如今看上去就明顯簡單多了。

2.deferred對象

var nanowrimoing = $.Deferred();
var wordGoal = 5000;
nanowrimoing.progress(function(wordCount) {
var percentComplete = Math.floor(wordCount / wordGoal * 100);
$('#indicator').text(percentComplete + '% complete');
});
nanowrimoing.done(function(){
$('#indicator').text('Good job!');
});

三.worker對象和多線程

四.異步腳本加載

1.傳統腳本在頁面中的位置

腳本分爲兩大類:阻塞式和非阻塞式。這裏的阻塞是指加載阻塞而不是運行阻塞。

<!DOCTYPE html>
<html>
<head>
<script src="headScript"></script>
<script defer src="deferredScript"></script>
</head>
<body>
    <script async defer src="chatWidget"></script>
    <script async defer src="asyncScript"></script>
</body>
</html>

 

上面這部分代碼是比較標準的關於腳本在一個頁面中的位置,1.其中傳統的未加任何修飾的headScript是阻塞式的腳本,因爲瀏覽器從上到下解釋執行JavaScript,因此這部分腳本文件在一開始就會被執行,而且在執行完以前是DOM是不會渲染的,可是head標籤裏面的css會加載。2.有defer屬性的腳本會在DOM渲染的同時進行加載,可是會在DOM渲染完畢以後纔開始執行,不幸的是,不是全部的瀏覽器都支持defer屬性,因此纔會有了jquery(function)這個東西。3.同時帶有async屬性和defer屬性時候,defer會覆蓋async,可是單獨有async的時候,腳本會在DOM渲染的時候加載而且運行。

2.可編程的腳本加載

若是不是一開始就在頁面種引入js文件,而是經過用戶交互來實現動態的加載js腳本,能夠經過編程方式加入。

瀏覽器獲取服務器腳本有2個方法,ajax獲取而且經過eval函數執行,另一個就是在DOM中插入<script>標籤,通常用第二種方法,由於瀏覽器幫助咱們生成HTTP請求以及eval會泄露做用域。

var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.src = '/js/feature.js';
head.appendChild(script);
script.onload = function() {
// 如今能夠調用腳本里定義的函數了
}

 

 不過,一些老版本的瀏覽器不支持onloa事件,因此可使用一些腳本加載庫來實現這一功能,如require.js。

相關文章
相關標籤/搜索