Rumah > hujung hadapan web > tutorial js > Pelajari gelung javascript dengan kemahiran me_javascript

Pelajari gelung javascript dengan kemahiran me_javascript

WBOY
Lepaskan: 2016-05-16 15:31:34
asal
1106 orang telah melayarinya

1. Gunakan tatasusunan dan bukannya jenis Objek untuk mewakili koleksi tersusun

Piawaian ECMAScript tidak menyatakan susunan storan sifat dalam jenis Objek JavaScript.

Tetapi apabila menggunakan gelung for..in untuk melintasi sifat dalam Object, anda perlu bergantung pada susunan tertentu. Tepat kerana ECMAScript tidak menyeragamkan jujukan ini secara eksplisit, setiap enjin pelaksanaan JavaScript boleh dilaksanakan mengikut ciri-cirinya sendiri, jadi ketekalan tingkah laku gelung for..in tidak boleh dijamin dalam persekitaran pelaksanaan yang berbeza.

Sebagai contoh, hasil kod berikut semasa memanggil kaedah laporan tidak pasti:

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 }]); 
// ? 
Salin selepas log masuk

Jika anda benar-benar perlu memastikan bahawa hasil yang dijalankan adalah berdasarkan susunan data, beri keutamaan untuk menggunakan jenis tatasusunan untuk mewakili data dan bukannya menggunakan jenis Objek secara langsung. Pada masa yang sama, cuba elakkan menggunakan for..in loops dan gunakan eksplisit for loops:

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" 
Salin selepas log masuk

Satu lagi tingkah laku yang sangat bergantung pada susunan ialah pengiraan nombor titik terapung:

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

Dalam Item 2, disebutkan bahawa operasi penambahan nombor titik terapung tidak dapat memenuhi undang-undang komutatif:
Keputusan (0.1 0.2) 0.3 dan 0.1 (0.2 0.3) masing-masing adalah
0.600000000000001 dan 0.6

Jadi untuk operasi aritmetik pada nombor titik terapung, susunan sewenang-wenangnya tidak boleh digunakan:

var total = 0, count = 0; 
for (var key in ratings) { // unpredictable order 
  total += ratings[key]; 
  count++; 
} 
total /= count; 
total; // &#63; 
Salin selepas log masuk

Apabila susunan traversal for..in berbeza, jumlah hasil akhir yang diperoleh juga berbeza Berikut adalah dua susunan pengiraan dan hasil yang sepadan:

(0.8 + 0.7 + 0.6 +0.9) / 4 // 0.75
(0.6 + 0.8 + 0.7 +0.9) / 4 // 0.7499999999999999
Salin selepas log masuk

Sudah tentu, untuk masalah seperti pengiraan nombor titik terapung, satu penyelesaian adalah dengan menggunakan integer untuk mewakilinya Sebagai contoh, kami mula-mula membesarkan nombor titik terapung di atas 10 kali menjadi data integer, dan kemudian mengurangkannya selepas itu. pengiraan selesai 10 kali:

(8+ 7 + 6 + 9) / 4 / 10  // 0.75
(6+ 8 + 7 + 9) / 4 / 10  // 0.75
Salin selepas log masuk

2. Jangan sekali-kali menambah atribut yang boleh dikira pada Object.prototype

Jika kod anda bergantung pada untuk..dalam gelung untuk mengulang sifat dalam jenis Objek, jangan tambahkan sebarang sifat yang boleh dikira pada Object.prototype.

Walau bagaimanapun, apabila mempertingkatkan persekitaran pelaksanaan JavaScript, selalunya perlu menambah sifat atau kaedah baharu pada objek Object.prototype. Sebagai contoh, anda boleh menambah kaedah untuk mendapatkan semua nama atribut dalam objek:

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

Tetapi hasilnya seperti ini:

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

Penyelesaian yang mungkin adalah menggunakan fungsi dan bukannya menentukan kaedah baharu pada Object.prototype:

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

Tetapi jika anda benar-benar perlu menambah harta baharu pada Object.prototype dan tidak mahu harta itu dilalui dalam gelung for..in, anda boleh menggunakan kaedah Object.defineProject yang disediakan oleh persekitaran ES5:

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 
}); 
Salin selepas log masuk

Bahagian utama kod di atas adalah untuk menetapkan atribut yang boleh dihitung kepada palsu. Dalam kes ini, harta itu tidak boleh dilalui dalam gelung for..in.

3. Untuk traversal tatasusunan, gunakan untuk gelung dan bukannya untuk..dalam gelung

Walaupun isu ini telah disebut dalam Item sebelumnya, untuk kod berikut, bolehkah anda melihat apakah purata akhir?

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; // &#63; 
Salin selepas log masuk

Mengikut pengiraan, keputusan akhir hendaklah 88.

Tetapi jangan lupa bahawa dalam gelung for..in, apa yang dilalui sentiasa menjadi kunci, bukan nilai Perkara yang sama berlaku untuk tatasusunan. Oleh itu, skor dalam gelung for..in di atas bukanlah siri nilai yang dijangkakan seperti 98, 74, dsb., tetapi satu siri indeks seperti 0, 1, dsb.

Jadi anda mungkin fikir keputusan akhir ialah:
(0 1 … 6) / 7 = 21

Tetapi jawapan ini juga salah. Satu lagi perkara penting ialah jenis kunci dalam gelung for..in sentiasa jenis rentetan, jadi pengendali di sini sebenarnya melakukan operasi penyambungan rentetan:

Jumlah akhir yang diperoleh sebenarnya rentetan 00123456. Nilai rentetan ini ditukar kepada jenis angka ialah 123456, dan kemudian dibahagikan dengan bilangan elemen, 7, untuk mendapatkan hasil akhir: 17636.571428571428

Jadi, untuk traversal tatasusunan, sebaiknya gunakan standard untuk gelung

4 Utamakan penggunaan kaedah traversal daripada gelung

Apabila menggunakan gelung, mudah melanggar prinsip KERING (Jangan Ulangi Sendiri). Ini kerana kita biasanya memilih kaedah salin-tampal untuk mengelakkan perenggan penyataan pekeliling tulisan tangan. Tetapi berbuat demikian akan menghasilkan banyak kod pendua dalam kod dan pembangun akan "mencipta semula roda" tanpa makna. Lebih penting lagi, adalah mudah untuk mengabaikan butiran dalam gelung semasa menyalin dan menampal, seperti nilai indeks permulaan, syarat penamatan, dsb.

Sebagai contoh, gelung berikut mempunyai masalah ini, dengan mengandaikan n ialah panjang objek koleksi:

for (var i = 0; i <= n; i++) { ... }
// 终止条件错误,应该是i < n
for (var i = 1; i < n; i++) { ... }
// 起始变量错误,应该是i = 0
for (var i = n; i >= 0; i--) { ... }
// 起始变量错误,应该是i = n - 1
for (var i = n - 1; i > 0; i--) { ... }
// 终止条件错误,应该是i >= 0
Salin selepas log masuk

可见在循环的一些细节处理上很容易出错。而利用JavaScript提供的闭包(参见Item 11),可以将循环的细节给封装起来供重用。实际上,ES5就提供了一些方法来处理这一问题。其中的Array.prototype.forEach是最简单的一个。利用它,我们可以将循环这样写:

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

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

Salin selepas log masuk

除了对集合对象进行遍历之外,另一种常见的模式是对原集合中的每个元素进行某种操作,然后得到一个新的集合,我们也可以利用forEach方法实现如下:

// 使用for循环
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());
});

Salin selepas log masuk

但是由于这种由将一个集合转换为另一个集合的模式十分常见,ES5也提供了Array.prototype.map方法用来让代码更加简单和优雅:

var trimmed = input.map(function(s) {
  return s.trim();
});
Salin selepas log masuk

另外,还有一种常见模式是对集合根据某种条件进行过滤,然后得到一个原集合的子集。ES5中提供了Array.prototype.filter来实现这一模式。该方法接受一个Predicate作为参数,它是一个返回true或者false的函数:返回true意味着该元素会被保留在新的集合中;返回false则意味着该元素不会出现在新集合中。比如,我们使用以下代码来对商品的价格进行过滤,仅保留价格在[min, max]区间的商品:

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

当然,以上的方法是在支持ES5的环境中可用的。在其它环境中,我们有两种选择: 1. 使用第三方库,如underscore或者lodash,它们都提供了相当多的通用方法来操作对象和集合。 2. 根据需要自行定义。

比如,定义如下的方法来根据某个条件取得集合中前面的若干元素:

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]

Salin selepas log masuk

为了更好的重用该方法,我们可以将它定义在Array.prototype对象上,具体的影响可以参考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]

Salin selepas log masuk

只有一个场合使用循环会比使用遍历函数要好:需要使用break和continue的时候。 比如,当使用forEach来实现上面的takeWhile方法时就会有问题,在不满足predicate的时候应该如何实现呢?

function takeWhile(a, pred) {
  var result = [];
  a.forEach(function(x, i) {
    if (!pred(x)) {
      // &#63;
    }
    result[i] = x;
  });
  return result;
}
Salin selepas log masuk

我们可以使用一个内部的异常来进行判断,但是它同样有些笨拙和低效:

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;
}
Salin selepas log masuk

可是使用forEach之后,代码甚至比使用它之前更加冗长。这显然是存在问题的。 对于这个问题,ES5提供了some和every方法用来处理存在提前终止的循环,它们的用法如下所示:

[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
Salin selepas log masuk

这两个方法都是短路方法(Short-circuiting):只要有任何一个元素在some方法的predicate中返回true,那么some就会返回;只有有任何一个元素在every方法的predicate中返回false,那么every方法也会返回false。

因此,takeWhile就可以实现如下:

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;
}
Salin selepas log masuk

实际上,这就是函数式编程的思想。在函数式编程中,你很少能够看见显式的for循环或者while循环。循环的细节都被很好地封装起来了。

5、总结

  • 在使用for..in循环时,不要依赖于遍历的顺序。
  • 当使用Object类型来保存数据时,需要保证其中的数据是无序的。
  • 当需要表示带有顺序的集合时,使用数组类型而不是Object类型。
  • 避免向Object.prototype中添加任何属性。
  • 如果确实有必要向Object.prototype中添加方法属性,可以考虑使用独立函数替代。
  • 使用Object.defineProperty来添加可以不被for..in循环遍历到的属性。
  • 当遍历数组时,使用标准的for循环,而不要使用for..in循环。
  • 在必要的场合考虑预先保存数组的长度,以提高性能。
  • 使用遍历方法Array.prototype.forEach和Array.prototype.map来代替循环,从而让代码更加清晰可读。
  • 对于重复出现的循环,可以考虑将它们进行抽象。通过第三方提供的方法或者自己实现。
  • 显式的循环在一些场合下还是有用武之地的,相应的也可以使用some或者every方法。

以上就是本文的全部内容,希望通过这篇文章大家更加了解javascript循环的原理,大家共同进步。

Label berkaitan:
sumber:php.cn
Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
Tutorial Popular
Lagi>
Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan