検索
ホームページウェブフロントエンドjsチュートリアルme_javascript スキルで JavaScript ループを学習する

1. オブジェクト型の代わりに配列を使用して、順序付けされたコレクションを表現します

ECMAScript 標準では、JavaScript のオブジェクト型でのプロパティの格納順序は指定されていません。

しかし、for..in ループを使用して Object のプロパティを走査する場合、特定の順序に依存する必要があります。 ECMAScript はこのシーケンスを明示的に標準化していないため、各 JavaScript 実行エンジンは独自の特性に従って実装される可能性があり、異なる実行環境での for..in ループの動作の一貫性は保証できません。

たとえば、レポート メソッドを呼び出したときの次のコードの結果は不確かです:

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 }]); 
// ? 

実行結果がデータの順序に基づいていることを本当に確認する必要がある場合は、オブジェクト型を直接使用するのではなく、配列型を使用してデータを表すことを優先してください。同時に、for..in ループの使用を避け、明示的な for ループを使用するようにしてください:

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" 

特に順序に依存するもう 1 つの動作は、浮動小数点数の計算です。

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

項目 2 では、浮動小数点数の加算演算は交換法則さえ満たせないと述べました:
(0.1 0.2) 0.3 と 0.1 (0.2 0.3) の結果はそれぞれ
0.600000000000001 と 0.6

したがって、浮動小数点数の算術演算では、任意の順序を使用することはできません。

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

for..in の走査順序が異なると、得られる最終的な合計結果も異なります。次に、2 つの計算順序とそれに対応する結果を示します。

(0.8 + 0.7 + 0.6 +0.9) / 4 // 0.75
(0.6 + 0.8 + 0.7 +0.9) / 4 // 0.7499999999999999
もちろん、浮動小数点数の計算などの問題の場合、解決策の 1 つは、整数を使用して表現することです。たとえば、最初に上記の浮動小数点数を 10 倍に拡大して整数データにし、その後、それを縮小します。 10 回の計算が完了しました:

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

2. Object.prototype に列挙可能な属性を追加しないでください。

コードが for..in ループに依存して Object 型のプロパティを反復処理する場合は、列挙可能なプロパティを Object.prototype に追加しないでください。

ただし、JavaScript 実行環境を強化する場合、Object.prototype オブジェクトに新しいプロパティまたはメソッドを追加することが必要になることがよくあります。たとえば、オブジェクト内のすべての属性名を取得するメソッドを追加できます:

Object.prototype.allKeys = function() { 
  var result = []; 
  for (var key in this) { 
    result.push(key); 
  } 
  return result; 
}; 
しかし、結果は次のようになります:

({ a: 1, b: 2, c: 3}).allKeys(); // ["allKeys", "a", "b","c"]
考えられる解決策は、Object.prototype で新しいメソッドを定義する代わりに関数を使用することです:

function allKeys(obj) { 
  var result = []; 
  for (var key in obj) { 
    result.push(key); 
  } 
  return result; 
} 
しかし、本当に Object.prototype に新しいプロパティを追加する必要があり、そのプロパティを for..in ループ内で走査したくない場合は、ES5 環境によって提供される 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 
}); 
上記のコードの重要な部分は、列挙可能属性を false に設定することです。この場合、プロパティを for..in ループ内で走査することはできません。

3. 配列の走査には、for..in ループの代わりに for ループを使用します

この問題は前の項目でも言及されていますが、次のコードについて、最終的な平均がいくらになるかわかりますか?

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; 
計算すると、最終結果は 88 になるはずです。

ただし、for..in ループでは、値ではなく常にキーが扱われることを忘れないでください。配列についても同様です。したがって、上記の for..in ループのスコアは、98、74 などの予期される一連の値ではなく、0、1 などの一連のインデックスです。

最終結果は次のようになると思われるかもしれません:

(0 1 … 6) / 7 = 21

しかし、この答えも間違っています。もう 1 つの重要な点は、for..in ループ内のキーの型は常に文字列型であるため、ここでの演算子は実際に文字列の結合操作を実行することです。

取得された最終的な合計は、実際には文字列 00123456 です。数値型に変換されたこの文字列の値は 123456 で、要素数 7 で割ると、最終結果 17636.571428571428

が得られます。

したがって、配列の走査には、標準の for ループを使用するのが最善です

4. ループではなくトラバーサル メソッドの使用を優先します

ループを使用する場合、DRY (Don'trepeat Yourself) 原則に違反する可能性があります。これは、循環文の段落を手書きすることを避けるために、通常、コピー&ペーストの方法を選択するためです。しかし、そうするとコード内に重複コードが大量に含まれることになり、開発者は無意味に「車輪の再発明」をすることになります。さらに重要なのは、開始インデックス値や終了条件など、コピー アンド ペーストするときにループ内の詳細を見落としやすいことです。

たとえば、n がコレクション オブジェクトの長さと仮定すると、次の for ループにはこの問題があります。

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

可见在循环的一些细节处理上很容易出错。而利用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++;
});

除了对集合对象进行遍历之外,另一种常见的模式是对原集合中的每个元素进行某种操作,然后得到一个新的集合,我们也可以利用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());
});

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

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

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

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

当然,以上的方法是在支持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]

为了更好的重用该方法,我们可以将它定义在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]

只有一个场合使用循环会比使用遍历函数要好:需要使用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;
}

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

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之后,代码甚至比使用它之前更加冗长。这显然是存在问题的。 对于这个问题,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

这两个方法都是短路方法(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;
}

实际上,这就是函数式编程的思想。在函数式编程中,你很少能够看见显式的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循环的原理,大家共同进步。

声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
JavaScriptとWeb:コア機能とユースケースJavaScriptとWeb:コア機能とユースケースApr 18, 2025 am 12:19 AM

Web開発におけるJavaScriptの主な用途には、クライアントの相互作用、フォーム検証、非同期通信が含まれます。 1)DOM操作による動的なコンテンツの更新とユーザーインタラクション。 2)ユーザーエクスペリエンスを改善するためにデータを提出する前に、クライアントの検証が実行されます。 3)サーバーとのリフレッシュレス通信は、AJAXテクノロジーを通じて達成されます。

JavaScriptエンジンの理解:実装の詳細JavaScriptエンジンの理解:実装の詳細Apr 17, 2025 am 12:05 AM

JavaScriptエンジンが内部的にどのように機能するかを理解することは、開発者にとってより効率的なコードの作成とパフォーマンスのボトルネックと最適化戦略の理解に役立つためです。 1)エンジンのワークフローには、3つの段階が含まれます。解析、コンパイル、実行。 2)実行プロセス中、エンジンはインラインキャッシュや非表示クラスなどの動的最適化を実行します。 3)ベストプラクティスには、グローバル変数の避け、ループの最適化、constとletsの使用、閉鎖の過度の使用の回避が含まれます。

Python vs. JavaScript:学習曲線と使いやすさPython vs. JavaScript:学習曲線と使いやすさApr 16, 2025 am 12:12 AM

Pythonは、スムーズな学習曲線と簡潔な構文を備えた初心者により適しています。 JavaScriptは、急な学習曲線と柔軟な構文を備えたフロントエンド開発に適しています。 1。Python構文は直感的で、データサイエンスやバックエンド開発に適しています。 2。JavaScriptは柔軟で、フロントエンドおよびサーバー側のプログラミングで広く使用されています。

Python vs. JavaScript:コミュニティ、ライブラリ、リソースPython vs. JavaScript:コミュニティ、ライブラリ、リソースApr 15, 2025 am 12:16 AM

PythonとJavaScriptには、コミュニティ、ライブラリ、リソースの観点から、独自の利点と短所があります。 1)Pythonコミュニティはフレンドリーで初心者に適していますが、フロントエンドの開発リソースはJavaScriptほど豊富ではありません。 2)Pythonはデータサイエンスおよび機械学習ライブラリで強力ですが、JavaScriptはフロントエンド開発ライブラリとフレームワークで優れています。 3)どちらも豊富な学習リソースを持っていますが、Pythonは公式文書から始めるのに適していますが、JavaScriptはMDNWebDocsにより優れています。選択は、プロジェクトのニーズと個人的な関心に基づいている必要があります。

C/CからJavaScriptへ:すべてがどのように機能するかC/CからJavaScriptへ:すべてがどのように機能するかApr 14, 2025 am 12:05 AM

C/CからJavaScriptへのシフトには、動的なタイピング、ゴミ収集、非同期プログラミングへの適応が必要です。 1)C/Cは、手動メモリ管理を必要とする静的に型付けられた言語であり、JavaScriptは動的に型付けされ、ごみ収集が自動的に処理されます。 2)C/Cはマシンコードにコンパイルする必要がありますが、JavaScriptは解釈言語です。 3)JavaScriptは、閉鎖、プロトタイプチェーン、約束などの概念を導入します。これにより、柔軟性と非同期プログラミング機能が向上します。

JavaScriptエンジン:実装の比較JavaScriptエンジン:実装の比較Apr 13, 2025 am 12:05 AM

さまざまなJavaScriptエンジンは、各エンジンの実装原則と最適化戦略が異なるため、JavaScriptコードを解析および実行するときに異なる効果をもたらします。 1。語彙分析:ソースコードを語彙ユニットに変換します。 2。文法分析:抽象的な構文ツリーを生成します。 3。最適化とコンパイル:JITコンパイラを介してマシンコードを生成します。 4。実行:マシンコードを実行します。 V8エンジンはインスタントコンピレーションと非表示クラスを通じて最適化され、Spidermonkeyはタイプ推論システムを使用して、同じコードで異なるパフォーマンスパフォーマンスをもたらします。

ブラウザを超えて:現実世界のJavaScriptブラウザを超えて:現実世界のJavaScriptApr 12, 2025 am 12:06 AM

現実世界におけるJavaScriptのアプリケーションには、サーバー側のプログラミング、モバイルアプリケーション開発、モノのインターネット制御が含まれます。 2。モバイルアプリケーションの開発は、ReactNativeを通じて実行され、クロスプラットフォームの展開をサポートします。 3.ハードウェアの相互作用に適したJohnny-Fiveライブラリを介したIoTデバイス制御に使用されます。

next.jsを使用してマルチテナントSaaSアプリケーションを構築する(バックエンド統合)next.jsを使用してマルチテナントSaaSアプリケーションを構築する(バックエンド統合)Apr 11, 2025 am 08:23 AM

私はあなたの日常的な技術ツールを使用して機能的なマルチテナントSaaSアプリケーション(EDTECHアプリ)を作成しましたが、あなたは同じことをすることができます。 まず、マルチテナントSaaSアプリケーションとは何ですか? マルチテナントSaaSアプリケーションを使用すると、Singの複数の顧客にサービスを提供できます

See all articles

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

AI Hentai Generator

AI Hentai Generator

AIヘンタイを無料で生成します。

ホットツール

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

このプロジェクトは osdn.net/projects/mingw に移行中です。引き続きそこでフォローしていただけます。 MinGW: GNU Compiler Collection (GCC) のネイティブ Windows ポートであり、ネイティブ Windows アプリケーションを構築するための自由に配布可能なインポート ライブラリとヘッダー ファイルであり、C99 機能をサポートする MSVC ランタイムの拡張機能が含まれています。すべての MinGW ソフトウェアは 64 ビット Windows プラットフォームで実行できます。

SublimeText3 英語版

SublimeText3 英語版

推奨: Win バージョン、コードプロンプトをサポート!

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

Eclipse を SAP NetWeaver アプリケーション サーバーと統合します。

PhpStorm Mac バージョン

PhpStorm Mac バージョン

最新(2018.2.1)のプロフェッショナル向けPHP統合開発ツール