Vincent Ko

VK's Blog

性能向上のテクニック:Setと配列メソッドを使用してJavaScriptコードを最適化する

フロントエンドコードの作成と保守の過程で、パフォーマンス最適化が必要なシーンにしばしば直面します。特に配列やオブジェクトを扱う際に、JavaScript が提供するメソッドを適切に使用することで、コードの実行効率を向上させるだけでなく、コードをより簡潔で理解しやすくすることができます。本記事では、実際のビジネスコードのレビューから、JavaScript の配列および集合メソッドを合理的に活用して最適化を達成する方法を探ります。

ビジネスシーン#

まずは一般的なシーンを見てみましょう。ユーザーが選択した各権限範囲に対して、適切な権限が割り当てられているかを検証する必要があります。この要件は非常に直接的に聞こえますが、実際の実装では、一連の配列操作が関与する可能性があります。これが今日最適化するソースコードです:

// 初期の権限検証ロジック
const selectedObjItem = [];
this.selectedPermissions.forEach((item) => {
  !selectedObjItem.includes(item.action_id) && selectedObjItem.push(item.action_id);
});

const result = this.selectedScopes.every(scope => {
  if (scope.has_instance) {
    return selectedObjItem.includes(scope.action_id);
  }
  return true;
})

if (!result) {
  showMsg(this.$t('AUTH:オブジェクト範囲を選択してください'), 'warning');
  return false;
}

このコードの目的は、選択された各シーン (selectedScopes) が権限範囲を選択しているかを検証し、これらの権限範囲が selectedPermissions に一致する必要があることです(各権限オブジェクトには一意の識別子 action_id があります)。

コードは表面的には期待通りの効果を達成していますが、詳細に分析すると、実際には最適化の余地があることがわかります。実際、いくつかの簡単な変更を加えることで、実行効率を向上させ、コードをより明確にすることができます。そのために、以下のいくつかの問題を考慮することができます:

  1. 配列の記録と重複排除を実現するために、より効率的な方法はありますか?
  2. every は選択を検証するための最良の方法ですか?
  3. 最終結果の判断は、すべての遍歴が終了するまで待つ必要がありますか?

最適化#

1. 重複排除と記録#

最初の記録は実際には重複排除も含まれており、主に push 前の証拠判定: !selectedObjItem.includes(item.action_id) に依存しています。重複排除が関与する場合、ES6 の自然で効率的なデータ型 Set を使用することを考えることができます。なぜなら、Set 自体が重複排除の特性を持っているため、コードを次のように最適化できます:

const selectedObjItem = new Set();
this.selectedPermissions.forEach((item) => {
  selectedObjItem.add(item.action_id);
});

Set を使用して重複を排除し、has メソッドを使用して要素の存在を確認することは、通常、配列の重複排除(pushincludes を組み合わせて確認する方法)よりも効率的です。その理由は次のとおりです:

  1. 時間計算量
    • Setadd メソッドと has メソッドの時間計算量は通常定数時間計算量 O (1) です。これは、Set 内部がハッシュテーブルを使用して実装されているため、データの迅速な検索と挿入が可能です。
    • 配列の includes メソッドの時間計算量は O (n) であり、最悪の場合、特定の要素が存在するかどうかを判断するために配列全体を遍歴する必要があります。
  2. 重複排除
    • Set データ構造は、設計上、重複要素のない集合であり、要素の一意性を自動的に管理します。既に Set に存在する要素を追加しようとすると、Set は何の操作も行わないため、手動で重複排除のロジックを書く必要はありません。
    • 配列で重複を排除する場合、追加する要素が一意であることを保証するために、追加のロジック(通常は includes を使用して確認した後に push)を書く必要があり、これがコードの複雑性と実行の操作ステップを増加させます。
  3. コードの簡潔性
    • Set を使用すると、コードがより簡潔で理解しやすくなります。Set のインターフェースは明確な意図を持ち、集合というデータ構造を表しているため、コードの意図がより明確で、意味が良くなります;
    • 配列の重複排除に関与する includespush メソッドは、どちらも配列メソッドであり理解しやすいですが、重複排除を実現するために組み合わせて使用する必要があり、コードが直感的でなく見えることがあります。

Set の使用と応用については、[下文](## 附:Set の说明与用例) で紹介します。

2. 検出の最適化#

ソースコードで every メソッドを使用することは可能ですが、同時に非効率的です。なぜなら、every はすべての項目を遍歴する必要があり、今回の検証の目的はすべての項目が選択されていることなので、実際には 1 つでも選択されていないものがあれば、早期にエラー結果を返すことができ、後続の内容を確認する必要はありません。

このような考え方があれば、some メソッドを思い出すのは簡単です。コードを次のように最適化できます:

const result = this.selectedScopes.some(scope => {
  if (scope.has_instance) {
    return !selectedObjItem.has(scope.action_id);
  }
  return false;
});

コードのロジックと構造は元のものと変わらないように見えますが、実際には効率が向上します。これは、sometrue を返した後、後続のループを続行しないためです。

3. 完全なコード#

最適化された完全なコードは次のとおりです:

// 重複排除して action_id を記録
const selectedObjItem = new Set();
this.selectedPermissions.forEach((item) => {
  selectedObjItem.add(item.action_id);
});

// すべての選択肢が選択されているかを検出
const result = this.selectedScopes.some(scope => {
  if (scope.has_instance) {
    return !selectedObjItem.has(scope.action_id);
  }
  return false;
});

if (result) {
  showMsg(this.$t('AUTH:オブジェクト範囲を選択してください'), 'warning');
  return false;
}

シーントレーニング#

似たような例を見てみましょう:

batchAddSource が二次元配列で、その値が [['a','b'], ['c','d'], ...] であるとします。selectedListbatchAddSource のサブセットで、選択された内容を表します。現在、選択された内容を渡すと未選択の内容を返すメソッド getUnselectedList が必要です。

思考#

この問題の核心は重複排除の問題であり、前述の最適化の考え方を組み合わせると、次のようなアイデアが得られます:

  1. 二次元配列の比較では、内側の配列をより比較しやすい文字列型(join メソッド)に変換できます。
  2. Set データ構造の has メソッドを使用して存在を判断します。
const getUnselectedList = (selectedList, batchAddSource) => {
	const selectedSource = new Set(selectedList.map(item => item.join('.')))
	return batchAddSource.filter(item => !selectedSource.has(item.join('.')))
}

まとめ#

JavaScript は進化し続ける言語であり、ES6 バージョンは新しい構文と機能を導入し、コード作成の効率と可読性を大幅に向上させました。ここでは、言及された幾つかのメソッドと応用を簡単にまとめます。

  1. 新しいデータ構造 Set の使用:配列の重複排除と非重複値の効率的な記録機能を非常に便利に実現できます。従来の遍歴チェック方法と比較して、Set のネイティブメカニズムは集合内の一意性を保証し、特に大量のデータを処理する際に効率を大幅に向上させる役割を果たします。
  2. 配列メソッドの合理的な使用:ES6 は forEacheverysome などの配列メソッドを提供しており、これらのメソッドを合理的に使用することで、コードをより明確にし、異なるビジネスロジックの要件に応じて最も適した反復方法を選択できます。例えば、some を使用して早期に結果を返すことで、不必要な遍歴を避けることができます。
  3. 論理判断の最適化:論理的な最適化を通じて、早期にループを終了させることで、不必要な計算を避け、リソースと時間を節約できます。配列や集合の検証操作を実装する際には、境界条件やショートサーキットロジックを考慮することで、より効率的なコード実行が可能です。
  4. ES6 の簡潔な構文の活用:アロー関数、テンプレート文字列などの新機能を使用することで、コードをより簡潔にし、冗長なコードを避け、コードの理解しやすさを向上させることができます。

実際のプログラミング実践において、各最適化ポイントは微小なパフォーマンス向上をもたらすかもしれませんが、全体としてこれらの改善が合わさることで、アプリケーションのパフォーマンスとユーザー体験を大幅に向上させることができます。上述の最適化手法を総合的に活用することで、効率的で良好な可読性を持つ JavaScript コードを作成することができます。

附:Set の説明と用例#

Set は特別なタイプの集合であり、各値は一度だけ出現することができます

上文で Set の一つの応用シーンを紹介しましたが、ここではこのデータ型の特性を簡単に振り返ります。主なメソッドは次のとおりです:

  • new Set(iterable) —— set を作成します。iterable オブジェクト(通常は配列)を提供すると、配列から値を set にコピーします。
  • set.add(value) —— 値を追加し、set 自体を返します。
  • set.delete(value) —— 値を削除します。このメソッド呼び出し時に value が存在する場合は true を返し、そうでない場合は false を返します。
  • set.has(value) —— valueset に存在する場合は true を返し、そうでない場合は false を返します。
  • set.clear() —— set を空にします。
  • set.size —— 要素の数を返します。

主な特徴は、同じ値を set.add(value) で繰り返し呼び出しても、何も変わらないことです。これが Set 内の各値が一度だけ出現する理由です。

カウント(統計)#

以下のコードを見て、コメントなしで何をしているのか、関連する原理を理解できるか確認してください:

const uid = () =>
  String(
    Date.now().toString(32) +
      Math.random().toString(16)
  ).replace(/\./g, '')

const size = 1000000
const set = new Set(new Array(size)
  .fill(0)
  .map(() => uid()))

console.log(
  size === set.size ? 'すべてのIDは一意です' : `一意でないレコード ${size - set.size}`
)

そうです、実際には uid 関数を検証して、生成された ID が一意であるかどうかを確認しています。興味深いのは、ここで Set をどのように利用して一意性を判断しているかです。なぜ set.size === size で生成された UUID が一意であるかを判断できるのでしょうか?

それは、Set 集合内の値は一度だけ出現するため、map を使用して uid() を生成する際に、同じ値があれば集合に追加されないからです。この特性を利用して、最初の例で重複値があるかどうか、重複の数を統計することができます:

  • set.size を呼び出すと、すべてが一意であれば、map 後の集合の長さと最初に定義した size の長さが一致します;
  • そうでなければ、sizeset.size の差が重複の数です。

Set を合理的に使用することで、コードをより効率的にし、より洗練され、理解しやすくすることができます。そのため、重複排除、統計、カウントなどのシーンに直面した場合、以前はためらわずに配列を使用していた場合でも、Set を使用してコードの効率を向上させることができるかどうかを考慮することが重要です。

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。