亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

跟我學(xué)習(xí)javascript的循環(huán)

 更新時(shí)間:2015年11月18日 10:07:35   作者:小平果118  
跟我學(xué)習(xí)javascript的循環(huán),本文不僅針對(duì)javascript循環(huán)進(jìn)行講解,還對(duì)prototype補(bǔ)充了幾點(diǎn)小tips,歡迎大家閱讀。

1、優(yōu)先使用數(shù)組而不是Object類型來表示有順序的集合

ECMAScript標(biāo)準(zhǔn)并沒有規(guī)定對(duì)JavaScript的Object類型中的屬性的存儲(chǔ)順序。

但是在使用for..in循環(huán)對(duì)Object中的屬性進(jìn)行遍歷的時(shí)候,確實(shí)是需要依賴于某種順序的。正因?yàn)镋CMAScript沒有對(duì)這個(gè)順序進(jìn)行明確地規(guī)范,所以每個(gè)JavaScript執(zhí)行引擎都能夠根據(jù)自身的特點(diǎn)進(jìn)行實(shí)現(xiàn),那么在不同的執(zhí)行環(huán)境中就不能保證for..in循環(huán)的行為一致性了。

比如,以下代碼在調(diào)用report方法時(shí)的結(jié)果就是不確定的:

function report(highScores) { 
  var result = ""; 
  var i = 1; 
  for (var name in highScores) { // unpredictable order 
    result += i + ". " + name + ": " + 
    highScores[name] + "\n"; 
    i++; 
  } 
  return result; 
} 
report([{ name: "Hank", points: 1110100 }, 
{ name: "Steve", points: 1064500 }, 
{ name: "Billy", points: 1050200 }]); 
// ? 

如果你確實(shí)需要保證運(yùn)行的結(jié)果是建立在數(shù)據(jù)的順序上,優(yōu)先使用數(shù)組類型來表示數(shù)據(jù),而不是直接使用Object類型。同時(shí),也盡量避免使用for..in循環(huán),而使用顯式的for循環(huán):

function report(highScores) { 
  var result = ""; 
  for (var i = 0, n = highScores.length; i < n; i++) { 
    var score = highScores[i]; 
    result += (i + 1) + ". " + 
    score.name + ": " + score.points + "\n"; 
  } 
  return result; 
} 
report([{ name: "Hank", points: 1110100 }, 
{ name: "Steve", points: 1064500 }, 
{ name: "Billy", points: 1050200 }]); 
// "1. Hank: 1110100 2. Steve: 1064500 3. Billy: 1050200\n" 

另一個(gè)特別依賴于順序的行為是浮點(diǎn)數(shù)的計(jì)算:

var ratings = { 
  "Good Will Hunting": 0.8, 
  "Mystic River": 0.7, 
  "21": 0.6, 
  "Doubt": 0.9 
}; 

在Item 2中,談到了浮點(diǎn)數(shù)的加法操作甚至不能滿足交換律:
(0.1 + 0.2) + 0.3 的結(jié)果和 0.1 + (0.2 + 0.3)的結(jié)果分別是
0.600000000000001 和 0.6

所以對(duì)于浮點(diǎn)數(shù)的算術(shù)操作,更加不能使用任意的順序了:

var total = 0, count = 0; 
for (var key in ratings) { // unpredictable order 
  total += ratings[key]; 
  count++; 
} 
total /= count; 
total; // ? 

當(dāng)for..in的遍歷順序不一樣時(shí),最后得到的total結(jié)果也就不一樣了,以下是兩種計(jì)算順序和其對(duì)應(yīng)的結(jié)果:

(0.8 + 0.7 + 0.6 +0.9) / 4 // 0.75
(0.6 + 0.8 + 0.7 +0.9) / 4 // 0.7499999999999999

當(dāng)然,對(duì)于浮點(diǎn)數(shù)的計(jì)算這一類問題,有一個(gè)解決方案是使用整型數(shù)來表示,比如我們將上面的浮點(diǎn)數(shù)首先放大10倍變成整型數(shù)據(jù),然后計(jì)算結(jié)束之后再縮小10倍:

(8+ 7 + 6 + 9) / 4 / 10  // 0.75
(6+ 8 + 7 + 9) / 4 / 10  // 0.75

2、絕不要向Object.prototype中添加可列舉的(Enumerable)屬性

如果你的代碼中依賴于for..in循環(huán)來遍歷Object類型中的屬性的話,不要向Object.prototype中添加任何可列舉的屬性。

但是在對(duì)JavaScript執(zhí)行環(huán)境進(jìn)行增強(qiáng)的時(shí)候,往往都需要向Object.prototype對(duì)象添加新的屬性或者方法。比如可以添加一個(gè)方法用于得到某個(gè)對(duì)象中的所有的屬性名:

Object.prototype.allKeys = function() { 
  var result = []; 
  for (var key in this) { 
    result.push(key); 
  } 
  return result; 
}; 

但是結(jié)果是下面這個(gè)樣子的:

({ a: 1, b: 2, c: 3}).allKeys(); // ["allKeys", "a", "b","c"]

一個(gè)可行的解決方案是使用函數(shù)而不是在Object.prototype上定義新的方法:

function allKeys(obj) { 
  var result = []; 
  for (var key in obj) { 
    result.push(key); 
  } 
  return result; 
} 

但是如果你確實(shí)需要向Object.prototype上添加新的屬性,同時(shí)也不希望該屬性在for..in循環(huán)中被遍歷到,那么可以利用ES5環(huán)境提供的Object.defineProject方法:

Object.defineProperty(Object.prototype, "allKeys", { 
  value: function() { 
    var result = []; 
    for (var key in this) { 
      result.push(key); 
    } 
    return result; 
  }, 
  writable: true, 
  enumerable: false, 
  configurable: true 
}); 

以上代碼的關(guān)鍵部分就是將enumerable屬性設(shè)置為false。這樣的話,在for..in循環(huán)中就無法遍歷該屬性了。

3、對(duì)于數(shù)組遍歷,優(yōu)先使用for循環(huán),而不是for..in循環(huán)

雖然上個(gè)Item已經(jīng)說過這個(gè)問題,但是對(duì)于下面這段代碼,能看出最后的平均數(shù)是多少嗎?

var scores = [98, 74, 85, 77, 93, 100, 89]; 
var total = 0; 
for (var score in scores) { 
  total += score; 
} 
var mean = total / scores.length; 
mean; // ? 

通過計(jì)算,最后的結(jié)果應(yīng)該是88。

但是不要忘了在for..in循環(huán)中,被遍歷的永遠(yuǎn)是key,而不是value,對(duì)于數(shù)組同樣如此。因此上述for..in循環(huán)中的score并不是期望的98, 74等一系列值,而是0, 1等一系列索引。

所以你也許會(huì)認(rèn)為最后的結(jié)果是:
(0 + 1+ …+ 6) / 7 = 21

但是這個(gè)答案也是錯(cuò)的。另外一個(gè)關(guān)鍵點(diǎn)在于,for..in循環(huán)中key的類型永遠(yuǎn)都是字符串類型,因此這里的+操作符執(zhí)行的實(shí)際上是字符串的拼接操作:

最后得到的total實(shí)際上是字符串00123456。這個(gè)字符串轉(zhuǎn)換成數(shù)值類型后的值是123456,然后再將它除以元素的個(gè)數(shù)7,就得到了最后的結(jié)果:17636.571428571428

所以,對(duì)于數(shù)組遍歷,還是使用標(biāo)準(zhǔn)的for循環(huán)最好

4、優(yōu)先使用遍歷方法而非循環(huán)

在使用循環(huán)的時(shí)候,很容易違反DRY(Don't Repeat Yourself)原則。這是因?yàn)槲覀兺ǔ?huì)選擇復(fù)制粘貼的方法來避免手寫一段段的循環(huán)語句。但是這樣做回讓代碼中出現(xiàn)大量重復(fù)代碼,開發(fā)人員也在沒有意義地”重復(fù)造輪子”。更重要的是,在復(fù)制粘貼的時(shí)候很容易忽視循環(huán)中的那些細(xì)節(jié),比如起始索引值,終止判斷條件等。

比如以下的for循環(huán)就存在這個(gè)問題,假設(shè)n是集合對(duì)象的長(zhǎng)度:

for (var i = 0; i <= n; i++) { ... }
// 終止條件錯(cuò)誤,應(yīng)該是i < n
for (var i = 1; i < n; i++) { ... }
// 起始變量錯(cuò)誤,應(yīng)該是i = 0
for (var i = n; i >= 0; i--) { ... }
// 起始變量錯(cuò)誤,應(yīng)該是i = n - 1
for (var i = n - 1; i > 0; i--) { ... }
// 終止條件錯(cuò)誤,應(yīng)該是i >= 0

可見在循環(huán)的一些細(xì)節(jié)處理上很容易出錯(cuò)。而利用JavaScript提供的閉包(參見Item 11),可以將循環(huán)的細(xì)節(jié)給封裝起來供重用。實(shí)際上,ES5就提供了一些方法來處理這一問題。其中的Array.prototype.forEach是最簡(jiǎn)單的一個(gè)。利用它,我們可以將循環(huán)這樣寫:

// 使用for循環(huán)
for (var i = 0, n = players.length; i < n; i++) {
  players[i].score++;
}

// 使用forEach
players.forEach(function(p) {
  p.score++;
});

除了對(duì)集合對(duì)象進(jìn)行遍歷之外,另一種常見的模式是對(duì)原集合中的每個(gè)元素進(jìn)行某種操作,然后得到一個(gè)新的集合,我們也可以利用forEach方法實(shí)現(xiàn)如下:

// 使用for循環(huán)
var trimmed = [];
for (var i = 0, n = input.length; i < n; i++) {
  trimmed.push(input[i].trim());
}

// 使用forEach
var trimmed = [];
input.forEach(function(s) {
  trimmed.push(s.trim());
});

但是由于這種由將一個(gè)集合轉(zhuǎn)換為另一個(gè)集合的模式十分常見,ES5也提供了Array.prototype.map方法用來讓代碼更加簡(jiǎn)單和優(yōu)雅:

var trimmed = input.map(function(s) {
  return s.trim();
});

另外,還有一種常見模式是對(duì)集合根據(jù)某種條件進(jìn)行過濾,然后得到一個(gè)原集合的子集。ES5中提供了Array.prototype.filter來實(shí)現(xiàn)這一模式。該方法接受一個(gè)Predicate作為參數(shù),它是一個(gè)返回true或者false的函數(shù):返回true意味著該元素會(huì)被保留在新的集合中;返回false則意味著該元素不會(huì)出現(xiàn)在新集合中。比如,我們使用以下代碼來對(duì)商品的價(jià)格進(jìn)行過濾,僅保留價(jià)格在[min, max]區(qū)間的商品:

listings.filter(function(listing) {
  return listing.price >= min && listing.price <= max;
});

當(dāng)然,以上的方法是在支持ES5的環(huán)境中可用的。在其它環(huán)境中,我們有兩種選擇: 1. 使用第三方庫,如underscore或者lodash,它們都提供了相當(dāng)多的通用方法來操作對(duì)象和集合。 2. 根據(jù)需要自行定義。

比如,定義如下的方法來根據(jù)某個(gè)條件取得集合中前面的若干元素:

function takeWhile(a, pred) {
  var result = [];
  for (var i = 0, n = a.length; i < n; i++) {
    if (!pred(a[i], i)) {
      break;
    }
    result[i] = a[i];
  }
  return result;
}

var prefix = takeWhile([1, 2, 4, 8, 16, 32], function(n) {
  return n < 10;
}); // [1, 2, 4, 8]

為了更好的重用該方法,我們可以將它定義在Array.prototype對(duì)象上,具體的影響可以參考Item 42。

Array.prototype.takeWhile = function(pred) {
  var result = [];
  for (var i = 0, n = this.length; i < n; i++) {
    if (!pred(this[i], i)) {
      break;
    }
    result[i] = this[i];
  }
  return result; 
};

var prefix = [1, 2, 4, 8, 16, 32].takeWhile(function(n) {
  return n < 10;
}); // [1, 2, 4, 8]

只有一個(gè)場(chǎng)合使用循環(huán)會(huì)比使用遍歷函數(shù)要好:需要使用break和continue的時(shí)候。 比如,當(dāng)使用forEach來實(shí)現(xiàn)上面的takeWhile方法時(shí)就會(huì)有問題,在不滿足predicate的時(shí)候應(yīng)該如何實(shí)現(xiàn)呢?

function takeWhile(a, pred) {
  var result = [];
  a.forEach(function(x, i) {
    if (!pred(x)) {
      // ?
    }
    result[i] = x;
  });
  return result;
}

我們可以使用一個(gè)內(nèi)部的異常來進(jìn)行判斷,但是它同樣有些笨拙和低效:

function takeWhile(a, pred) {
  var result = [];
  var earlyExit = {}; // unique value signaling loop break
  try {
    a.forEach(function(x, i) {
      if (!pred(x)) {
        throw earlyExit;
      }
      result[i] = x;
    });
  } catch (e) {
    if (e !== earlyExit) { // only catch earlyExit
      throw e;
    }
  }
  return result;
}

可是使用forEach之后,代碼甚至比使用它之前更加冗長(zhǎng)。這顯然是存在問題的。 對(duì)于這個(gè)問題,ES5提供了some和every方法用來處理存在提前終止的循環(huán),它們的用法如下所示:

[1, 10, 100].some(function(x) { return x > 5; }); // true
[1, 10, 100].some(function(x) { return x < 0; }); // false

[1, 2, 3, 4, 5].every(function(x) { return x > 0; }); // true
[1, 2, 3, 4, 5].every(function(x) { return x < 3; }); // false

這兩個(gè)方法都是短路方法(Short-circuiting):只要有任何一個(gè)元素在some方法的predicate中返回true,那么some就會(huì)返回;只有有任何一個(gè)元素在every方法的predicate中返回false,那么every方法也會(huì)返回false。

因此,takeWhile就可以實(shí)現(xiàn)如下:

function takeWhile(a, pred) {
  var result = [];
  a.every(function(x, i) {
    if (!pred(x)) {
      return false; // break
    }
    result[i] = x;
    return true; // continue
  });
  return result;
}

實(shí)際上,這就是函數(shù)式編程的思想。在函數(shù)式編程中,你很少能夠看見顯式的for循環(huán)或者while循環(huán)。循環(huán)的細(xì)節(jié)都被很好地封裝起來了。

5、總結(jié)

  • 在使用for..in循環(huán)時(shí),不要依賴于遍歷的順序。
  • 當(dāng)使用Object類型來保存數(shù)據(jù)時(shí),需要保證其中的數(shù)據(jù)是無序的。
  • 當(dāng)需要表示帶有順序的集合時(shí),使用數(shù)組類型而不是Object類型。
  • 避免向Object.prototype中添加任何屬性。
  • 如果確實(shí)有必要向Object.prototype中添加方法屬性,可以考慮使用獨(dú)立函數(shù)替代。
  • 使用Object.defineProperty來添加可以不被for..in循環(huán)遍歷到的屬性。
  • 當(dāng)遍歷數(shù)組時(shí),使用標(biāo)準(zhǔn)的for循環(huán),而不要使用for..in循環(huán)。
  • 在必要的場(chǎng)合考慮預(yù)先保存數(shù)組的長(zhǎng)度,以提高性能。
  • 使用遍歷方法Array.prototype.forEach和Array.prototype.map來代替循環(huán),從而讓代碼更加清晰可讀。
  • 對(duì)于重復(fù)出現(xiàn)的循環(huán),可以考慮將它們進(jìn)行抽象。通過第三方提供的方法或者自己實(shí)現(xiàn)。
  • 顯式的循環(huán)在一些場(chǎng)合下還是有用武之地的,相應(yīng)的也可以使用some或者every方法。

以上就是本文的全部?jī)?nèi)容,希望通過這篇文章大家更加了解javascript循環(huán)的原理,大家共同進(jìn)步。

相關(guān)文章

最新評(píng)論