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

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

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

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

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

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

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

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

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

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

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

我們以一段實(shí)例代碼來(lái)進(jìn)行詳細(xì)說(shuō)明。

復(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.

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

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



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

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

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


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

我們先來(lái)看看一段大家都非常熟悉的代碼。

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

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

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

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

我們?cè)倩仡櫼幌律厦娴牧鞒虉D,發(fā)現(xiàn)有什麼倪端沒(méi)?
真正的循環(huán)體不僅有我們的邏輯代碼,還包含了實(shí)現(xiàn)循環(huán)自身的執(zhí)行判斷和處理代碼。 也就是說(shuō),i < list.length這個(gè)判斷條件是每一次循環(huán)前都要執(zhí)行的。而 javascript 中,對(duì)對(duì)象的屬性或方法進(jìn)行讀取時(shí),需要進(jìn)行一次查詢。
似乎明白了點(diǎn)什麼了吧?這個(gè)判斷條件存在兩個(gè)操作:1. 從list數(shù)組中查詢length屬性;2. 比較i與list.length的大小。
假設(shè)list數(shù)組含有 n 個(gè)元素,則程序需要在這個(gè)循環(huán)的執(zhí)行判斷中進(jìn)行 2n 次操作。

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

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

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

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

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

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

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

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

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

在循環(huán)中,我們得到循環(huán)當(dāng)前的數(shù)組元素自然是為了對(duì)其或利用其進(jìn)行一些操作,這不免會(huì)出現(xiàn)對(duì)該元素?cái)?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
 */

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

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

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

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

復(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;

這樣看起來(lái)的確美觀了不少。

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

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

有點(diǎn)像 emcascript5 中的foreach,不過(guò)這兩者之間差別狠大,這里不多做解釋。

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

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

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

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

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

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

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

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

我們常常這個(gè)方法來(lái)進(jìn)行對(duì)對(duì)象的操作。

復(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
 */

 如果你曾使用過(guò) mongodb,那你對(duì)它的 query 機(jī)制絕對(duì)不會(huì)陌生。 因?yàn)?mongodb 的 query 機(jī)制就像是它的 api 的靈魂,靈活的 curd 操作方式為 mongodb 贏得了不少人氣和發(fā)展動(dòng)力。

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

復(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)本身,而是對(duì)你需要進(jìn)行歷遍的對(duì)象進(jìn)行優(yōu)化。
就比如說(shuō) nanodb 中的 nanocollection 類(lèi),雖然表面看上去就是一個(gè)數(shù)組, 存有所有的元素,或是一個(gè)對(duì)象,用元素的 id 作為鍵,然后對(duì)元素進(jìn)行存儲(chǔ)。

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

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

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

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

如果你是需要使用循環(huán)對(duì)象來(lái)對(duì)對(duì)象的某些屬性的值進(jì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進(jìn)行對(duì)象查詢并沒(méi)有太大的可優(yōu)化之處,一切都還需從實(shí)際需求出發(fā)。: p

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

while () {}的執(zhí)行順序與for () {}類(lèi)似,執(zhí)行判斷在邏輯代碼之前,不過(guò)省去了初始化和處理代碼。
當(dāng)給予的條件時(shí),便執(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 ()同樣不需要計(jì)數(shù)器,而是通過(guò)某些條件來(lái)判斷是否執(zhí)行或繼續(xù)執(zhí)行邏輯代碼。

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

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

但這兩種循環(huán)是危險(xiǎn)的,因?yàn)樗鼈兡J(rèn)只受執(zhí)行條件的控制,如果一旦邏輯代碼內(nèi)一直沒(méi)有對(duì)執(zhí)行判斷產(chǎn)生任何影響,就會(huì)出現(xiàn)死循環(huán)。

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

var sum = 02

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

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

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

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

為了地瞭解continue語(yǔ)句的作用,我們還是先來(lái)看看一段實(shí)例代碼

復(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ā)佈廣播的客戶端在集閤中的下標(biāo)
  var index   = clients.indexOf(this);

  for (var i = clients.length - 1; i >= 0; --i) {
    if (i === index) {
      // 如果為發(fā)佈廣播的客戶端,則結(jié)束當(dāng)前循環(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模塊實(shí)現(xiàn)了一個(gè) broadcast server,在其中的broadcast方法中,我們使用了continue語(yǔ)句, 用以實(shí)現(xiàn)將信息向除發(fā)佈廣播的客戶端外的所有已建立連接的客戶端。

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

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

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

這是因?yàn)閎reak語(yǔ)句和continue語(yǔ)句允許接受由一個(gè)定義好的 label 名稱,以進(jìn)行代碼跳轉(zhuǎn)。

我們來(lái)看看 mdn 提供的實(shí)例代碼。

復(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"

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

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

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

5. 高級(jí)循環(huán)

5.1 展開(kāi)循環(huán)

我們先來(lái)看看兩段代碼,你猜猜哪一個(gè)的性能更加。

復(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]);
  }
}


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

這里我們來(lái)看看一種更給力的解決方案。 如果一個(gè)業(yè)務(wù)環(huán)節(jié)中需要對(duì)大數(shù)據(jù)集進(jìn)行迭代處理,而這個(gè)數(shù)據(jù)集從開(kāi)始迭代起,數(shù)據(jù)量不會(huì)再改變, 那麼可以考慮採(cǎi)用一種名為 duff 裝置的技術(shù)。這項(xiàng)技術(shù)是以其的創(chuàng)造者 tom duff 的名字來(lái)命名的, 這項(xiàng)技術(shù)最先實(shí)現(xiàn)於 c 語(yǔ)言。后來(lái) jeff greenberg 將其移植到 javascript 中,并經(jīng)過(guò) 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ù)的工作原理是通過(guò)計(jì)算values的長(zhǎng)度除以 8 以得到需要迭代的次數(shù),并以math.floor()函數(shù)來(lái)保證結(jié)果為整數(shù), 然后再計(jì)算不能被 8 整除時(shí)的餘數(shù),并對(duì)這些元素單獨(dú)進(jìn)行處理,其餘則 8 次為單次展開(kāi)次數(shù)來(lái)進(jìn)行迭代。

我將這種裝置再加以封裝,可以得到一種帶有異步味道的 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) {
  //...
});

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

5.2 非原生循環(huán)

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

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

復(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

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

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

在 node.js 社區(qū)中,遞歸被用來(lái)實(shí)現(xiàn)一種非常重要的技術(shù):中間件技術(shù)。 這是一段尚未公佈的新版本的 webjs 中的中間件實(shí)現(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ù)雜,不過(guò)如果我們對(duì)其精簡(jiǎn)之后,就清晰許多了。

復(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)的實(shí)現(xiàn),是因?yàn)檫f歸是最適合 node.js 中異步 i/o 的程序流程響應(yīng)方式。

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

相關(guān)文章

最新評(píng)論