欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入理解JavaScript編程中的同步與異步機(jī)制

 更新時間:2015年06月24日 15:10:14   投稿:goldensun  
這篇文章主要介紹了深入理解JavaScript編程中的同步與異步機(jī)制,不僅僅是AJAX已經(jīng)深入到了各個角落,Node.js的火爆也讓JS的異步編程格外引人注目,需要的朋友可以參考下

 JavaScript的優(yōu)勢之一是其如何處理異步代碼。異步代碼會被放入一個事件隊(duì)列,等到所有其他代碼執(zhí)行后才進(jìn)行,而不會阻塞線程。然而,對于初學(xué)者來說,書寫異步代碼可能會比較困難。而在這篇文章里,我將會消除你可能會有的任何困惑。
理解異步代碼

JavaScript最基礎(chǔ)的異步函數(shù)是setTimeout和setInterval。setTimeout會在一定時間后執(zhí)行給定的函數(shù)。它接受一個回調(diào)函數(shù)作為第一參數(shù)和一個毫秒時間作為第二參數(shù)。以下是用法舉例:
 

console.log( "a" );
setTimeout(function() {
  console.log( "c" )
}, 500 );
setTimeout(function() {
  console.log( "d" )
}, 500 );
setTimeout(function() {
  console.log( "e" )
}, 500 );
console.log( "b" );

正如預(yù)期,控制臺先輸出“a”、“b”,大約500毫秒后,再看到“c”、“d”、“e”。我用“大約”是因?yàn)閟etTimeout事實(shí)上是不可預(yù)知的。實(shí)際上,甚至 HTML5規(guī)范都提到了這個問題:

  •     “這個API不能保證計(jì)時會如期準(zhǔn)確地運(yùn)行。由于CPU負(fù)載、其他任務(wù)等所導(dǎo)致的延遲是可以預(yù)料到的?!?/li>


有趣的是,直到在同一程序段中所有其余的代碼執(zhí)行結(jié)束后,超時才會發(fā)生。所以如果設(shè)置了超時,同時執(zhí)行了需長時間運(yùn)行的函數(shù),那么在該函數(shù)執(zhí)行完成之前,超時甚至都不會啟動。實(shí)際上,異步函數(shù),如setTimeout和setInterval,被壓入了稱之為Event Loop的隊(duì)列。

Event Loop是一個回調(diào)函數(shù)隊(duì)列。當(dāng)異步函數(shù)執(zhí)行時,回調(diào)函數(shù)會被壓入這個隊(duì)列。JavaScript引擎直到異步函數(shù)執(zhí)行完成后,才會開始處理事件循環(huán)。這意味著JavaScript代碼不是多線程的,即使表現(xiàn)的行為相似。事件循環(huán)是一個先進(jìn)先出(FIFO)隊(duì)列,這說明回調(diào)是按照它們被加入隊(duì)列的順序執(zhí)行的。JavaScript被 node選做為開發(fā)語言,就是因?yàn)閷戇@樣的代碼多么簡單啊。

Ajax

異步Javascript與XML(AJAX)永久性的改變了Javascript語言的狀況。突然間,瀏覽器不再需要重新加載即可更新web頁面。 在不同的瀏覽器中實(shí)現(xiàn)Ajax的代碼可能漫長并且乏味;但是,幸虧有jQuery(還有其他庫)的幫助,我們能夠以很容易并且優(yōu)雅的方式實(shí)現(xiàn)客戶端-服務(wù)器端通訊。

我們可以使用jQuery跨瀏覽器接口$.ajax很容易地檢索數(shù)據(jù),然而卻不能呈現(xiàn)幕后發(fā)生了什么。比如:

var data;
$.ajax({
  url: "some/url/1",
  success: function( data ) {
    // But, this will!
    console.log( data );
  }
})
// Oops, this won't work...
console.log( data );

較容易犯的錯誤,是在調(diào)用$.ajax之后馬上使用data,但是實(shí)際上是這樣的:
 

xmlhttp.open( "GET", "some/ur/1", true );
xmlhttp.onreadystatechange = function( data ) {
  if ( xmlhttp.readyState === 4 ) {
    console.log( data );
  }
};
xmlhttp.send( null );

底層的XmlHttpRequest對象發(fā)起請求,設(shè)置回調(diào)函數(shù)用來處理XHR的readystatechnage事件。然后執(zhí)行XHR的send方法。在XHR運(yùn)行中,當(dāng)其屬性readyState改變時readystatechange事件就會被觸發(fā),只有在XHR從遠(yuǎn)端服務(wù)器接收響應(yīng)結(jié)束時回調(diào)函數(shù)才會觸發(fā)執(zhí)行。

處理異步代碼

異步編程很容易陷入我們常說的“回調(diào)地獄”。因?yàn)槭聦?shí)上幾乎JS中的所有異步函數(shù)都用到了回調(diào),連續(xù)執(zhí)行幾個異步函數(shù)的結(jié)果就是層層嵌套的回調(diào)函數(shù)以及隨之而來的復(fù)雜代碼。

node.js中的許多函數(shù)也是異步的。因此如下的代碼基本上很常見:
 

var fs = require( "fs" );
fs.exists( "index.js", function() {
  fs.readFile( "index.js", "utf8", function( err, contents ) {
    contents = someFunction( contents ); // do something with contents
    fs.writeFile( "index.js", "utf8", function() {
      console.log( "whew! Done finally..." );
    });
  });
});
console.log( "executing..." );

下面的客戶端代碼也很多見:

 

GMaps.geocode({
  address: fromAddress,
  callback: function( results, status ) {
    if ( status == "OK" ) {
      fromLatLng = results[0].geometry.location;
      GMaps.geocode({
        address: toAddress,
        callback: function( results, status ) {
          if ( status == "OK" ) {
            toLatLng = results[0].geometry.location;
            map.getRoutes({
              origin: [ fromLatLng.lat(), fromLatLng.lng() ],
              destination: [ toLatLng.lat(), toLatLng.lng() ],
              travelMode: "driving",
              unitSystem: "imperial",
              callback: function( e ){
                console.log( "ANNNND FINALLY here's the directions..." );
                // do something with e
              }
            });
          }
        }
      });
    }
  }
});

Nested callbacks can get really nasty, but there are several solutions to this style of coding.

嵌套的回調(diào)很容易帶來代碼中的“壞味道”,不過你可以用以下的幾種風(fēng)格來嘗試解決這個問題

  •     The problem isn't with the language itself; it's with the way programmers use the language — Async Javascript.

    沒有糟糕的語言,只有糟糕的程序猿 ——異步JavaSript


命名函數(shù)

清除嵌套回調(diào)的一個便捷的解決方案是簡單的避免雙層以上的嵌套。傳遞一個命名函數(shù)給作為回調(diào)參數(shù),而不是傳遞匿名函數(shù):
 

var fromLatLng, toLatLng;
var routeDone = function( e ){
  console.log( "ANNNND FINALLY here's the directions..." );
  // do something with e
};
var toAddressDone = function( results, status ) {
  if ( status == "OK" ) {
    toLatLng = results[0].geometry.location;
    map.getRoutes({
      origin: [ fromLatLng.lat(), fromLatLng.lng() ],
      destination: [ toLatLng.lat(), toLatLng.lng() ],
      travelMode: "driving",
      unitSystem: "imperial",
      callback: routeDone
    });
  }
};
var fromAddressDone = function( results, status ) {
  if ( status == "OK" ) {
    fromLatLng = results[0].geometry.location;
    GMaps.geocode({
      address: toAddress,
      callback: toAddressDone
    });
  }
};
GMaps.geocode({
  address: fromAddress,
  callback: fromAddressDone
});

此外, async.js 庫可以幫助我們處理多重Ajax requests/responses. 例如:
 

async.parallel([
  function( done ) {
    GMaps.geocode({
      address: toAddress,
      callback: function( result ) {
        done( null, result );
      }
    });
  },
  function( done ) {
    GMaps.geocode({
      address: fromAddress,
      callback: function( result ) {
        done( null, result );
      }
    });
  }
], function( errors, results ) {
  getRoute( results[0], results[1] );
});

這段代碼執(zhí)行兩個異步函數(shù),每個函數(shù)都接收一個名為"done"的回調(diào)函數(shù)并在函數(shù)結(jié)束的時候調(diào)用它。當(dāng)兩個"done"回調(diào)函數(shù)結(jié)束后,parallel函數(shù)的回調(diào)函數(shù)被調(diào)用并執(zhí)行或處理這兩個異步函數(shù)產(chǎn)生的結(jié)果或錯誤。

Promises模型
引自 CommonJS/A:

  •     promise表示一個操作獨(dú)立完成后返回的最終結(jié)果。

有很多庫都包含了promise模型,其中jQuery已經(jīng)有了一個可使用且很出色的promise API。jQuery在1.5版本引入了Deferred對象,并可以在返回promise的函數(shù)中使用jQuery.Deferred的構(gòu)造結(jié)果。而返回promise的函數(shù)則用于執(zhí)行某種異步操作并解決完成后的延遲。
 

var geocode = function( address ) {
  var dfd = new $.Deferred();
  GMaps.geocode({
    address: address,
    callback: function( response, status ) {
      return dfd.resolve( response );
    }
  });
  return dfd.promise();
};
var getRoute = function( fromLatLng, toLatLng ) {
  var dfd = new $.Deferred();
  map.getRoutes({
    origin: [ fromLatLng.lat(), fromLatLng.lng() ],
    destination: [ toLatLng.lat(), toLatLng.lng() ],
    travelMode: "driving",
    unitSystem: "imperial",
    callback: function( e ) {
      return dfd.resolve( e );
    }
  });
  return dfd.promise();
};
var doSomethingCoolWithDirections = function( route ) {
  // do something with route
};
$.when( geocode( fromAddress ), geocode( toAddress ) ).
  then(function( fromLatLng, toLatLng ) {
    getRoute( fromLatLng, toLatLng ).then( doSomethingCoolWithDirections );
  });

這允許你執(zhí)行兩個異步函數(shù)后,等待它們的結(jié)果,之后再用先前兩個調(diào)用的結(jié)果來執(zhí)行另外一個函數(shù)。

  •     promise表示一個操作獨(dú)立完成后返回的最終結(jié)果。

在這段代碼里,geocode方法執(zhí)行了兩次并返回了一個promise。異步函數(shù)之后執(zhí)行,并在其回調(diào)里調(diào)用了resolve。然后,一旦兩次調(diào)用resolve完成,then將會執(zhí)行,其接收了之前兩次調(diào)用geocode的返回結(jié)果。結(jié)果之后被傳入getRoute,此方法也返回一個promise。最終,當(dāng)getRoute的promise解決后,doSomethingCoolWithDirections回調(diào)就執(zhí)行了。
 
事件
事件是另一種當(dāng)異步回調(diào)完成處理后的通訊方式。一個對象可以成為發(fā)射器并派發(fā)事件,而另外的對象則監(jiān)聽這些事件。這種類型的事件處理方式稱之為 觀察者模式 。 backbone.js 庫在withBackbone.Events中就創(chuàng)建了這樣的功能模塊。
 

var SomeModel = Backbone.Model.extend({
  url: "/someurl"
});
var SomeView = Backbone.View.extend({
  initialize: function() {
    this.model.on( "reset", this.render, this );
    this.model.fetch();
  },
  render: function( data ) {
    // do something with data
  }
});
var view = new SomeView({
  model: new SomeModel()
});

還有其他用于發(fā)射事件的混合例子和函數(shù)庫,例如 jQuery Event Emitter , EventEmitter , monologue.js ,以及node.js內(nèi)建的 EventEmitter 模塊。

  •     事件循環(huán)是一個回調(diào)函數(shù)的隊(duì)列。

一個類似的派發(fā)消息的方式稱為 中介者模式 , postal.js 庫中用的即是這種方式。在中介者模式,有一個用于所有對象監(jiān)聽和派發(fā)事件的中間人。在這種模式下,一個對象不與另外的對象產(chǎn)生直接聯(lián)系,從而使得對象間都互相分離。

絕不要返回promise到一個公用的API。這不僅關(guān)系到了API用戶對promises的使用,也使得重構(gòu)更加困難。不過,內(nèi)部用途的promises和外部接口的事件的結(jié)合,卻可以讓應(yīng)用更低耦合且便于測試。

在先前的例子里面,doSomethingCoolWithDirections回調(diào)函數(shù)在兩個geocode函數(shù)完成后執(zhí)行。然后,doSomethingCoolWithDirections才會獲得從getRoute接收到的響應(yīng),再將其作為消息發(fā)送出去。
 

var doSomethingCoolWithDirections = function( route ) {
  postal.channel( "ui" ).publish( "directions.done", {
    route: route
  });
};

這允許了應(yīng)用的其他部分不需要直接引用產(chǎn)生請求的對象,就可以響應(yīng)異步回調(diào)。而在取得命令時,很可能頁面的好多區(qū)域都需要更新。在一個典型的jQuery Ajax過程中,當(dāng)接收到的命令變化時,要順利的回調(diào)可能就得做相應(yīng)的調(diào)整了。這可能會使得代碼難以維護(hù),但通過使用消息,處理UI多個區(qū)域的更新就會簡單得多了。
 

var UI = function() {
  this.channel = postal.channel( "ui" );
  this.channel.subscribe( "directions.done", this.updateDirections ).withContext( this );
};
UI.prototype.updateDirections = function( data ) {
  // The route is available on data.route, now just update the UI
};
app.ui = new UI();

另外一些基于中介者模式傳送消息的庫有 amplify, PubSubJS, and radio.js。

結(jié)論

JavaScript 使得編寫異步代碼很容易. 使用 promises, 事件, 或者命名函數(shù)來避免“callback hell”. 為獲取更多javascript異步編程信息,請點(diǎn)擊Async JavaScript: Build More Responsive Apps with Less . 更多的實(shí)例托管在github上,地址NetTutsAsyncJS,趕快Clone吧 !

相關(guān)文章

  • javascript如何創(chuàng)建表格(javascript繪制表格的二種方法)

    javascript如何創(chuàng)建表格(javascript繪制表格的二種方法)

    利用js來動態(tài)創(chuàng)建表格有兩種格式,appendChild()和insertRow、insertCell()。兩種方式其實(shí)差不多,但第一種有可能在IE上有問題,所以推薦大家使用第二種方法,看下面的解決和使用方法
    2013-12-12
  • javascript實(shí)現(xiàn)的一個隨機(jī)點(diǎn)名功能

    javascript實(shí)現(xiàn)的一個隨機(jī)點(diǎn)名功能

    這篇文章主要介紹了JS實(shí)現(xiàn)的一個隨機(jī)點(diǎn)名功能,邏輯簡單,用在班級或活動時是個不錯的選擇,需要的朋友可以參考下
    2014-08-08
  • JavaScript高級程序設(shè)計(jì)(第3版)學(xué)習(xí)筆記 概述

    JavaScript高級程序設(shè)計(jì)(第3版)學(xué)習(xí)筆記 概述

    在JavaScript面世之初,沒有人會想到它會被應(yīng)用的如此廣泛,也遠(yuǎn)比一般人想象中的要復(fù)雜強(qiáng)大的多,在我自己學(xué)習(xí)的過程中,曾經(jīng)有過多次震撼
    2012-10-10
  • 詳解JS預(yù)解析原理

    詳解JS預(yù)解析原理

    這篇文章主要介紹了JS預(yù)解析原理的相關(guān)知識,文中講解非常細(xì)致,代碼幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下
    2020-06-06
  • 最新評論