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

JavaScript中對循環(huán)語句的優(yōu)化技巧深入探討

 更新時間:2014年06月06日 10:39:31   作者:  
這篇文章主要介紹了JavaScript中對循環(huán)語句的優(yōu)化技巧深入探討,本文翻譯自一個臺灣朋友的文章,需要的朋友可以參考下

循環(huán)是所有編程語言中最為重要的機制之一,幾乎任何擁有實際意義的計算機程序(排序、查詢等)都里不開循環(huán)。 而循環(huán)也正是程序優(yōu)化中非常讓人頭疼的一環(huán),我們往往需要不斷去優(yōu)化程序的復(fù)雜度,卻因循環(huán)而糾結(jié)在時間復(fù)雜度和空間復(fù)雜度之間的抉擇。

在 javascript 中,有3種原生循環(huán),for () {}, while () {}和do {} while (),其中最為常用的要數(shù)for () {}。

然而for正是 javascript 工程師們在優(yōu)化程序時最容易忽略的一種循環(huán)。

我們先來回顧一下for的基本知識。
javascript 的for語法繼承自c語言,for循環(huán)的基本語法有兩種使用方法。

1. 循環(huán)數(shù)組

for循環(huán)的基本語法

復(fù)制代碼 代碼如下:

for ( /* 初始化 */2 /* 判斷條件 */2 /* 循環(huán)處理 */ ) {
  //... 邏輯代碼
}

我們以一段實例代碼來進行詳細說明。

復(fù)制代碼 代碼如下:

var array = [1, 2, 3, 4, 5];
var sum   = 0;

for (var i = 0, len = array.length; i < len; ++i) {
  sum += array[i];
}

console.log('The sum of the array\'s items is %d.', sum);
//=> The sum of the array's items is 15.

在這段代碼中,我們首先定義并初始化了一個用存儲待累加項的數(shù)組和一個總和整形變量。 接下來,我們開始進行循環(huán)。在該for循環(huán)的初始化代碼中,我們也定義并初始化了兩個變量: i(計數(shù)器)和len(循環(huán)數(shù)組長度的別名),當i小於len時,循環(huán)條件成立,執(zhí)行邏輯代碼;每次邏輯代碼執(zhí)行完畢以后,i自增1。

在循環(huán)的邏輯代碼中,我們把當前循環(huán)的數(shù)組項加到總和變量中。
這個循環(huán)用流程圖表示為如下:



從這個流程圖中我們不難發(fā)現(xiàn),程序中真正的循環(huán)體不僅有我們的邏輯代碼,還包含了實現(xiàn)循環(huán)自身的執(zhí)行判斷和循環(huán)處理。
這樣,我們的優(yōu)化思路就清晰了,我們可以從四個方面進行優(yōu)化。

1.循環(huán)體前的初始化代碼
2.循環(huán)體中的執(zhí)行判斷條件
3.邏輯代碼
4.邏輯代碼后的處理代碼

ps: 其中第一點和第二點存在重要關(guān)系。


1.1 優(yōu)化初始化代碼和執(zhí)行判斷條件

我們先來看看一段大家都非常熟悉的代碼。

復(fù)制代碼 代碼如下:

// wrong!
for (var i = 02 i < list.length2 ++i) {
  //... 邏輯代碼
}

相信現(xiàn)在大部分寫著 javascript 的工程師依然使用著這段看似狠正常的循環(huán)方法,但為什麼我在這里說它是錯誤的呢?
我們把這個循環(huán)的所有東西都拆開來看看:

1.初始化代碼 - 這段循環(huán)只定義并初始化了一個計數(shù)器變量。
2.執(zhí)行判斷條件 - 當計數(shù)器小於list的長度時成立。
3.處理代碼 - 計數(shù)器自增1。

我們再回顧一下上面的流程圖,發(fā)現(xiàn)有什麼倪端沒?
真正的循環(huán)體不僅有我們的邏輯代碼,還包含了實現(xiàn)循環(huán)自身的執(zhí)行判斷和處理代碼。 也就是說,i < list.length這個判斷條件是每一次循環(huán)前都要執(zhí)行的。而 javascript 中,對對象的屬性或方法進行讀取時,需要進行一次查詢。
似乎明白了點什麼了吧?這個判斷條件存在兩個操作:1. 從list數(shù)組中查詢length屬性;2. 比較i與list.length的大小。
假設(shè)list數(shù)組含有 n 個元素,則程序需要在這個循環(huán)的執(zhí)行判斷中進行 2n 次操作。

如果我們把代碼改成這樣:

復(fù)制代碼 代碼如下:

// Well
for (var i = 0, len = list.length; i < len; ++i) {
  //...
}

在這段改進后的代碼中,我們在循環(huán)體執(zhí)行前的初始化代碼中, 增加定義并初始化了一個len變量,用於存儲list.length的值(關(guān)於變量、表達式、指針和值的相關(guān)內(nèi)容將在第二篇中討論)。 這樣,我們在循環(huán)體中的執(zhí)行判斷中就無需再次對list數(shù)組進行屬性查詢,操作數(shù)為原先的一半。

以上步驟我們完善了算法的時間復(fù)雜度,而如果要繼續(xù)優(yōu)化空間復(fù)雜度的話,要如何做呢? 如果你的邏輯代碼不受循環(huán)順序限制,那你可以嘗試以下優(yōu)化方式。

復(fù)制代碼 代碼如下:

for (var i = list.length - 1; i >= 0; --i) {
  //...
}

這段代碼通過把循環(huán)順序倒置,把i計數(shù)器從最后一個元素下標(list.length - 1)開始,向前循環(huán)。 以達到把循環(huán)所需變量數(shù)減到 1 個,而且在執(zhí)行判斷中,降低了變量查詢的次數(shù),減少了執(zhí)行 cpu 指令前的耗時。

1.2 優(yōu)化邏輯代碼

在循環(huán)中,我們得到循環(huán)當前的數(shù)組元素自然是為了對其或利用其進行一些操作,這不免會出現(xiàn)對該元素數(shù)次的調(diào)用。

復(fù)制代碼 代碼如下:

var array = [
  { name: 'Will Wen Gunn', type: 'hentai' },
  { name: 'Vill Lin', type: 'moegril' }
];

for (var i = array.length - 1; i >= 0; --i) {
  console.log('Name: %s', array[i].name);
  console.log('He/She is a(n) %s', array[i].type);

  console.log('\r\n');
}
/*=>
  Name: Vill Lin
  He/She is a(n) moegril

  Name: Will Wen Gunn
  He/She is a(n) hentai
 */

這段代碼中,程序需要對每個數(shù)組元素的name和type屬性進行查詢。 如果數(shù)組有 n 個元素,程序就進行了 4n 次對象查詢。

復(fù)制代碼 代碼如下:

1. array[i]
2. array[i].name
3. array[i]
4. array[i].type

相信此時你一定想到了解決方法了吧,那就是把當前數(shù)組元素的值賦值到一個變量中,然后在邏輯代碼中使用它。

復(fù)制代碼 代碼如下:

var array = [
  { name: 'Will Wen Gunn', type: 'hentai' },
  { name: 'Vill Lin', type: 'moegril' }
];
var person = null;

for (var i = array.length - 1; i >= 0 && (person = array[i]); --i) {
  console.log('Name: %s', person.name);
  console.log('He/She is a(n) %s', person.type);

  console.log('\r\n');
}
person = null;

這樣看起來的確美觀了不少。

復(fù)制代碼 代碼如下:

 1. array[i] => var person
 2. person.name
 3. person.type

有點像 emcascript5 中的foreach,不過這兩者之間差別狠大,這里不多做解釋。

ps:感謝大家的指正,經(jīng)過實驗得知,如果數(shù)組內(nèi)的元素是直接傳值定義的,則在循環(huán)中得到值一定是值,而非指針。 所以無論是定義表達式還是變量,都會有額外的內(nèi)存空間請求。

1.3 優(yōu)化處理代碼

實際上,循環(huán)體中的處理代碼并沒有太多東西可以進行優(yōu)化,i計數(shù)器也就是自增1就足夠了。
ps:如果有什麼好的建議或方法,歡迎提供。:)

2. 循環(huán)對象(object)

在 javascript 中,for還可以對 object 的屬性和方法進行歷遍。 需要注意的是,for循環(huán)無法對對象所屬的包裝類型或是構(gòu)造函數(shù)中原型屬性、方法(prototype)進行歷遍。

語法比循環(huán)數(shù)組還要簡單。

復(fù)制代碼 代碼如下:

for (/* 初始化 */ var key in object) {
  //... 邏輯代碼
}

我們常常這個方法來進行對對象的操作。

復(fù)制代碼 代碼如下:

var person = {
  'name'  : 'Will Wen Gunn',
  'type'  : 'hentai',
  'skill' : ['Programming', 'Photography', 'Speaking', 'etc']
};

for (var key in person) {
  value = person[key];

  // if the value is array, convert it to a string
  if (value instanceof Array) {
    value = value.join(', ');
  }

  console.log('%s: %s', key, value);
}
/*=>
 name: Will Wen Gunn
 type: hentai
 skill: Programming, Photography, Speaking, etc
 */

 如果你曾使用過 mongodb,那你對它的 query 機制絕對不會陌生。 因為 mongodb 的 query 機制就像是它的 api 的靈魂,靈活的 curd 操作方式為 mongodb 贏得了不少人氣和發(fā)展動力。

而在 nanodb 的 mongo api 實現(xiàn)中,query 的實現(xiàn)方式就大面積地使用了循環(huán)對象。

復(fù)制代碼 代碼如下:

var myDB   = nano.db('myDB');
var myColl = myDB.collection('myColl');

var _cursor = myColl.find({
  type     : 'repo',
  language : 'JavaScript'
});

_cursor
  .sort({
    star: 1
  })
  .toArray(function(err, rows) {
    if (err)
      return console.error(err);

    console.log(rows);
  });

而我們需要優(yōu)化的,并非循環(huán)本身,而是對你需要進行歷遍的對象進行優(yōu)化。
就比如說 nanodb 中的 nanocollection 類,雖然表面看上去就是一個數(shù)組, 存有所有的元素,或是一個對象,用元素的 id 作為鍵,然后對元素進行存儲。

但事實并非如此,曾經(jīng)使用過 underscore 的同學(xué)應(yīng)該會知道其中的_.invert方法。 這是一個相當有趣的方法,它把所傳入的對象的鍵與值反過來。

復(fù)制代碼 代碼如下:

var person = {
  'name' : 'Will Wen Gunn',
  'type' : 'hentai'
};

var _inverted = _.invert(person);
console.log(_inverted);
/*=>
 {
   'Will Wen Gunn' : 'name',
   'hentai'        : 'type'
 }
 */

如果你是需要使用循環(huán)對象來對對象的某些屬性的值進行查詢,那你就可以嘗試一下以下方法。

復(fù)制代碼 代碼如下:

var person = {
  'name' : 'Will Wen Gunn',
  'type' : 'hentai'
};

var name = 'Will Wen Gunn';

var _inverted = _.invert(person);

if (_inverted[name] === 'name') {
  console.log('Catched!');
}
//=> Catched!

然而利用for進行對象查詢并沒有太大的可優(yōu)化之處,一切都還需從實際需求出發(fā)。: p

接下來我們來看看其他兩種循環(huán),while () {}和do {} while ()。 相信任何接收過計算機科學(xué)課程的朋友都這兩個循環(huán)都不會陌生。他們唯一的區(qū)別就在與執(zhí)行循環(huán)體的邏輯順序。

while () {}的執(zhí)行順序與for () {}類似,執(zhí)行判斷在邏輯代碼之前,不過省去了初始化和處理代碼。
當給予的條件時,便執(zhí)行邏輯代碼,直到條件不再成立為止。

復(fù)制代碼 代碼如下:

var sum = 0;

while (sum < 10) {
  sum += sum + 1;
}

console.log(sum);
//=> 15

do {} while ()則是把執(zhí)行判斷放到了邏輯代碼之后,也就是“先斬后奏”。

復(fù)制代碼 代碼如下:

var sum = 0;

do {
  sum += sum + 1;
} while (sum < 10);

console.log(sum);
//=> 15

while () {}與do {} while ()同樣不需要計數(shù)器,而是通過某些條件來判斷是否執(zhí)行或繼續(xù)執(zhí)行邏輯代碼。

3. while () {}和do {} while ()

while () {}和do {} while ()主要用於業(yè)務(wù)邏輯中,為達到某一目的而不斷執(zhí)行一系列操作,如任務(wù)隊列。

但這兩種循環(huán)是危險的,因為它們默認只受執(zhí)行條件的控制,如果一旦邏輯代碼內(nèi)一直沒有對執(zhí)行判斷產(chǎn)生任何影響,就會出現(xiàn)死循環(huán)。

復(fù)制代碼 代碼如下:

var sum = 02

// warning!
while (sum < 10) {
  sum = 1 + 12
}

這樣的代碼無異於while (true) {},所以在使用之前,必須明確執(zhí)行條件和如何對執(zhí)行條件產(chǎn)生影響的方法。

4. 善用循環(huán)控制語句

相信所有 javascript 工程師都使用過break語句,但continue語句卻相對少用。 實際上,在不少優(yōu)秀的 javascript 開源項目中,都能發(fā)現(xiàn)continue的身影。

為了地瞭解continue語句的作用,我們還是先來看看一段實例代碼

復(fù)制代碼 代碼如下:

// Node.js Broadcast Server
var net  = require('net');
var util = require('util');

var broadcastServer = net.createServer();

// Client Store
broadcastServer.clients = [];

// Clients Broadcast Method
net.Socket.prototype.broadcast = function(msg) {
  var clients = broadcastServer.clients;
  // 獲得發(fā)佈廣播的客戶端在集閤中的下標
  var index   = clients.indexOf(this);

  for (var i = clients.length - 1; i >= 0; --i) {
    if (i === index) {
      // 如果為發(fā)佈廣播的客戶端,則結(jié)束當前循環(huán)體
      continue;
    }

    currClient = clients[i];

    if (!currClient.destroyed) {
      currClient.write(
        util.format(
          '\r[Echo Client %s:%d] %s\nInput: ',
          currClient.remoteAddress, currClient.remotePort, msg)
      );
    }
  }
};

// A new client connected
broadcastServer.on('connection', function(client) {
  broadcastServer.clients.push(client);

  // Welcome
  client.write('[Broadcast Server] Welcome!\nInput:');
  client.broadcast(client, 'Joined!');

  // Message handle
  client.on('data', function(msg) {
    client.broadcast(msg);
    client.write('\rInput:');
  });

  // Disconnect handle
  client.on('end', function() {
    client.broadcast('Left!');
  })
});

// Bind
broadcastServer.listen(8080, function() {
  console.log('Broadcast Server bound.');
});

這段代碼基於 node.js 的net模塊實現(xiàn)了一個 broadcast server,在其中的broadcast方法中,我們使用了continue語句, 用以實現(xiàn)將信息向除發(fā)佈廣播的客戶端外的所有已建立連接的客戶端。

代碼內(nèi)容相當簡單, 當某一客戶端需要向其他客戶端發(fā)佈廣播時,則調(diào)用該客戶端所對應(yīng)client對象的broadcast方法, 在broadcast方法中,程序會先獲取當前客戶端在以緩存的客戶端 socket 集合中的位置下標, 然后對所有客戶端 socket 進行循環(huán)發(fā)佈,當循環(huán)計數(shù)器來到之前獲得的位置下標,則跳過當前循環(huán)體中的邏輯代碼,繼續(xù)下一個循環(huán)。

相信學(xué)習(xí)過 c/c++ 語言的工程師都會從各種地方得到這樣一個忠告:“不要使用 goto 語句?!?/P>

而這個“臭名昭著”的goto語句其實就是一個代碼流程控制器,關(guān)於goto語句的詳細內(nèi)容這里不會詳細說明。 然而 javascript 沒有明顯的goto語句,但從break語句和continue語句中,不難發(fā)現(xiàn) javascript 中g(shù)oto的影子。

這是因為break語句和continue語句允許接受由一個定義好的 label 名稱,以進行代碼跳轉(zhuǎn)。

我們來看看 mdn 提供的實例代碼。

復(fù)制代碼 代碼如下:

var i, j;

loop1:
for (i = 0; i < 3; i++) {      //The first for statement is labeled "loop1"
   loop2:
   for (j = 0; j < 3; j++) {   //The second for statement is labeled "loop2"
      if (i == 1 && j == 1) {
         continue loop1;
      } else {
         console.log("i = " + i + ", j = " + j);
      }
   }
}

// Output is:
//   "i = 0, j = 0"
//   "i = 0, j = 1"
//   "i = 0, j = 2"
//   "i = 1, j = 0"
//   "i = 2, j = 0"
//   "i = 2, j = 1"
//   "i = 2, j = 2"
// Notice how it skips both "i = 1, j = 1" and "i = 1, j = 2"

在這段實例代碼中,實現(xiàn)了兩層循環(huán),而在每層循環(huán)外都定義了一個label,用於之后的continue語句進行調(diào)用。

第一層循環(huán)在loop1的 label 中,也就是說在后面的程序中,如果在continue語句或break語句選擇了loop1 label,就會跳出最外層循環(huán)。
第二層循環(huán)在頂層循環(huán)中的loop2的 label 中,若在continue語句或break語句中選擇了loop2 label,就會回到頂層循環(huán)的循環(huán)體內(nèi)。

通過使用循環(huán)控制語句,我們可以對原有的循環(huán)執(zhí)行判斷進行干涉,以至於可以構(gòu)建出十分復(fù)雜的邏輯系統(tǒng)。 說句題外話,linux kernel 中有非常多的goto語句,至於為什麼還是能經(jīng)常聽到不要用goto語句之流的言論,就自己 google 吧。

5. 高級循環(huán)

5.1 展開循環(huán)

我們先來看看兩段代碼,你猜猜哪一個的性能更加。

復(fù)制代碼 代碼如下:

// Setup
var array = [
  ["DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA"],
  ["DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA"],
  ["DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA"],
  ["DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA"],
  ["DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA"],
  ["DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA"],
  ["DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA"],
  ["DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA", "DATA"]
];
function process(item) {
  // Do something with item
}

// Case 1
for (var i = array.length - 1; i >= 0; i--) {
  for (var j = array[i].length - 1; j >= 0; i--) {
    process(array[i][j]);
  }
}

// Case 2
for (var i = array.length - 1; i >= 0; i = i - 4) {
  for (var j = array[i].length - 1; j >= 0; j = j - 6) {
    process(array[i][j]);
    process(array[i][j - 1]);
    process(array[i][j - 2]);
    process(array[i][j - 3]);
    process(array[i][j - 4]);
    process(array[i][j - 5]);
  }
  for (var j = array[i - 1].length - 1; j >= 0; j = j - 6) {
    process(array[i][j]);
    process(array[i][j - 1]);
    process(array[i][j - 2]);
    process(array[i][j - 3]);
    process(array[i][j - 4]);
    process(array[i][j - 5]);
  }
  for (var j = array[i - 2].length - 1; j >= 0; j = j - 6) {
    process(array[i][j]);
    process(array[i][j - 1]);
    process(array[i][j - 2]);
    process(array[i][j - 3]);
    process(array[i][j - 4]);
    process(array[i][j - 5]);
  }
  for (var j = array[i - 3].length - 1; j >= 0; j = j - 6) {
    process(array[i][j]);
    process(array[i][j - 1]);
    process(array[i][j - 2]);
    process(array[i][j - 3]);
    process(array[i][j - 4]);
    process(array[i][j - 5]);
  }
}


我需要對array中的所有子數(shù)組的元素進行歷遍,有兩種方案,一種是我們平常所使用的方法,另一種是把循環(huán)任務(wù)展開。 答案是 case 2 性能更好,因為在每 6 個元素之間的執(zhí)行判斷都全部刪除了,自然比往常的都要快。

這里我們來看看一種更給力的解決方案。 如果一個業(yè)務(wù)環(huán)節(jié)中需要對大數(shù)據(jù)集進行迭代處理,而這個數(shù)據(jù)集從開始迭代起,數(shù)據(jù)量不會再改變, 那麼可以考慮採用一種名為 duff 裝置的技術(shù)。這項技術(shù)是以其的創(chuàng)造者 tom duff 的名字來命名的, 這項技術(shù)最先實現(xiàn)於 c 語言。后來 jeff greenberg 將其移植到 javascript 中,并經(jīng)過 andrew b. king 修改并提出了一種更為高效的版本。

復(fù)制代碼 代碼如下:

//credit: Speed Up Up Your Site (New Riders, 2003)
var iterations = Math.floor(values.length / 8);
var leftover = values.length % 8;
var i = 0;

if (leftover > 0) {
  do {
    process(values[i++]);
  } while (--leftover > 0);
}
do {
  process(values[i++]);
  process(values[i++]);
  process(values[i++]);
  process(values[i++]);
  process(values[i++]);
  process(values[i++]);
  process(values[i++]);
  process(values[i++]);
} while (--iterations > 0);

這種技術(shù)的工作原理是通過計算values的長度除以 8 以得到需要迭代的次數(shù),并以math.floor()函數(shù)來保證結(jié)果為整數(shù), 然后再計算不能被 8 整除時的餘數(shù),并對這些元素單獨進行處理,其餘則 8 次為單次展開次數(shù)來進行迭代。

我將這種裝置再加以封裝,可以得到一種帶有異步味道的 api。

復(fù)制代碼 代碼如下:

function duff(array, mapper) {
  var n = Math.floor(array.length / 8);
  var l = array.length % 8;

  var i = 0;
  if (l > 0) {
    do {
      mapper(array[i++]);
    } while (--i > 0);
  }
  do {
    mapper(array[i++]);
    mapper(array[i++]);
    mapper(array[i++]);
    mapper(array[i++]);
    mapper(array[i++]);
    mapper(array[i++]);
    mapper(array[i++]);
    mapper(array[i++]);
  } while (--n > 0);
}

duff([...], function(item) {
  //...
});

這里是一組對於以上三種迭代解決方案的性能測試及其結(jié)果。http://jsperf.com/spreaded-loop

5.2 非原生循環(huán)

在任何編程語言中,能夠?qū)崿F(xiàn)循環(huán)的,不止語言所提供的原生循環(huán)語句,還可以通過其他方式來間接實現(xiàn)。

讓我們先來溫習(xí)一下高中數(shù)學(xué)的一點內(nèi)容——數(shù)列的通項公式。

復(fù)制代碼 代碼如下:

bacause
  a[1] = 1
  a[n] = 2 * a[n - 1] + 1

so
                   a[n] + 1 = 2 * a[n - 1] + 2
                            = 2 * (a[n - 1] + 1)
(a[n] + 1) / (a[n - 1] + 1) = 2

then
  a[n] + 1 = (a[n] + 1) / (a[n - 1] + 1) * (a[n - 1] + 1) / (a[n - 2] + 1) * ... * (a[2] + 1) / (a[1] + 1) * (a[i] + 1)
  a[n] + 1 = 2 * 2 * ... * 2 * 2
  a[n] + 1 = 2^n
      a[n] = 2^n - 1

final
  a[n] = 2^n - 1

看了上面這段簡單的演算,估計你也猜到我們將要討論的內(nèi)容了吧。 是的,我們還可以使用遞歸來實現(xiàn)循環(huán)。

遞歸是數(shù)學(xué)和計算機科學(xué)中非常重要的一種應(yīng)用方法,它是指函數(shù)在其使用時調(diào)用其自身。

在 node.js 社區(qū)中,遞歸被用來實現(xiàn)一種非常重要的技術(shù):中間件技術(shù)。 這是一段尚未公佈的新版本的 webjs 中的中間件實現(xiàn)代碼。

復(fù)制代碼 代碼如下:

/**
 * Middlewares run method
 * @param  {String} url Current request url
 * @param  {Object} req the request object
 * @param  {Object} res the response object
 * @param  {Function} out Complete Callback
 * @return {Function}     the server
 */
server.runMiddlewares = function(url, req, res, out) {
  var index = -1;

  var middlewares = this._usingMiddlewares;

  // run the next middleware if it is exists
  function next(err) {
    index++;

    // current middleware
    var curr = middlewares[index];

    if (curr) {
      var check = new RegExp(curr.route);

      // Check the route
      if (check.test(url)) {
        try {
          function later() {
            debug('A middleware says it need to be later on %s', url);
            // The dependencies do not right now
            if (middlewares.indexOf(curr) !== middlewares.length - 1) {
              _later(curr);
              index--;
              next();
            } else {
              debug('A middleware dependencies wrong');

              // This middleware can not run
              out();
            }
          }

          // Run the middleware
          if (utils.isFunc(curr.handler)) {

            // Normal middleware function
            curr.handler(req, res, next, later);

          } else if (utils.isObject(curr.handler) && utils.isFunc(curr.handler.emit)) {

            // Server object
            curr.handler.emit('request', req, res, next, later);

          } else {

            // There are something wrong about the middleware
            next();

          }
        } catch(err) {
          next();
        }
      } else {
        next();
      }
    } else {
      // Out to next step of the pipeline
      out();
    }
  }

  // if the middleware depend on other middlewares,
  // it can let it later to run
  function _later(curr) {
    var i = middlewares.indexOf(curr);
    var _tmp1 = middlewares.slice(0, i);
    _tmp1.push(middlewares[i + 1], curr);
    var _tmp2 = middlewares.slice(i + 2);
    [].push.apply(_tmp1, _tmp2);
    middlewares = _tmp1;
  }

  // first middleware
  next();

  return this;
};

雖然這段代碼看上去狠復(fù)雜,不過如果我們對其精簡之后,就清晰許多了。

復(fù)制代碼 代碼如下:

server.runMiddlewares = function(url, req, res, out) {
  var index = -1;

  var middlewares = this._usingMiddlewares;

  // run the next middleware if it is exists
  function next(err) {
    index++;

    // current middleware
    var curr = middlewares[index];

    if (curr) {
      var check = new RegExp(curr.route);

      // Check the route
      if (check.test(url)) {
          // run the current middleware
          curr.handler(req, res, next);
      } else {
        next();
      }
    } else {
      // Out to next step of the pipeline
      out();
    }
  }

  // first middleware
  next();

  return this;
};

遞歸之所以可以用於中間件系統(tǒng)的實現(xiàn),是因為遞歸是最適合 node.js 中異步 i/o 的程序流程響應(yīng)方式。

在這段中間件實現(xiàn)代碼中,this._usingmiddlewares為循環(huán)數(shù)組,function next()是循環(huán)體,其中check.test(url)為執(zhí)行判斷條件, 而循環(huán)處理代碼就是循環(huán)體中最前面的index計數(shù)器自增 1 和next函數(shù)自身的遞歸調(diào)用。

相關(guān)文章

  • javascript實現(xiàn)的時間格式加8小時功能示例

    javascript實現(xiàn)的時間格式加8小時功能示例

    這篇文章主要介紹了javascript實現(xiàn)的時間格式加8小時功能,涉及javascript日期時間轉(zhuǎn)換與運算相關(guān)操作技巧,需要的朋友可以參考下
    2019-06-06
  • javascript實現(xiàn)切換td中的值

    javascript實現(xiàn)切換td中的值

    這篇文章主要介紹了javascript實現(xiàn)切換td中的值的方法,需要的朋友可以參考下
    2014-12-12
  • BootStrap Table 獲取同行不同列元素的方法

    BootStrap Table 獲取同行不同列元素的方法

    表格同行中存在元素的相互調(diào)用,如何保證元素能夠被同行不同列的其他方框使用呢?下面通過實例代碼給大家介紹下,一起看看吧
    2016-12-12
  • JavaScript表單驗證實現(xiàn)代碼

    JavaScript表單驗證實現(xiàn)代碼

    這篇文章主要為大家詳細介紹了JavaScript表單驗證的實現(xiàn)代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-05-05
  • JS iFrame加載慢怎么解決

    JS iFrame加載慢怎么解決

    這篇文章主要介紹了JS iFrame加載慢的解決方法,非常實用,感興趣的朋友一起學(xué)習(xí)吧
    2016-05-05
  • JavaScript?CSS解析B站的彈幕可以不擋人物原理及技巧

    JavaScript?CSS解析B站的彈幕可以不擋人物原理及技巧

    這篇文章主要為大家介紹了JavaScript?CSS解析B站的彈幕可以不擋人物原理及技巧,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-01-01
  • 原生js添加節(jié)點appendChild、insertBefore方式

    原生js添加節(jié)點appendChild、insertBefore方式

    這篇文章主要介紹了原生js添加節(jié)點appendChild、insertBefore方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-10-10
  • javascript導(dǎo)出csv文件(excel)的方法示例

    javascript導(dǎo)出csv文件(excel)的方法示例

    這篇文章主要給大家介紹了關(guān)于javascript導(dǎo)出csv文件(excel)的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家學(xué)習(xí)或者使用javascript具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-08-08
  • JavaScript箭頭函數(shù)的五種使用方法及三點注意事項

    JavaScript箭頭函數(shù)的五種使用方法及三點注意事項

    這篇文章主要介紹了JavaScript箭頭函數(shù)的五種使用方法及三點注意事項,箭頭函數(shù)是ES6新增的定義函數(shù)的方式,文章圍繞主題展開詳細的內(nèi)容介紹,需要的朋友可以參考一下
    2022-08-08
  • 在webstorm中配置less的方法詳解

    在webstorm中配置less的方法詳解

    這篇文章主要介紹了在webstorm中配置less的方法,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2020-09-09

最新評論