面接の質問: for ループがあるのに、なぜ forEach が必要なのでしょうか?



js には for for...in for...of forEach など、ループがたくさんありますが、いくつかのループは似ているように感じますが、今日は for ループと forEach の違いについて説明します。いくつかの側面から議論します。

  1. for ループと forEach の本質的な違い。
  2. for ループと forEach の構文の違い。
  3. for ループと forEach のパフォーマンスの違い。

本質的な違い

for ループは、js が登場した頃から存在するループメソッドです。forEach は ES5 によって提案されたメソッドで、Array Set Map などの反復可能なオブジェクトのプロトタイプに実装されています。forEach は反復子であり、反復可能なオブジェクトの走査を担当します。では、トラバーサル、反復、反復可能オブジェクトとは何でしょうか?

トラバーサル: データ構造の各メンバーへの定期的および 1 回限りのアクセスを指します。

Iteration : Iteration は特別な形式の再帰であり、反復子によって提供されるメソッドです。デフォルトでは、データ構造のメンバーは特定の順序で 1 つずつアクセスされます。反復も走査動作です。

Iterable オブジェクト: ES6 で iterable 型が導入されました。Array Set Map String 引数 NodeList はすべて iterable に属します。その特徴は、すべて [Symbol.iterator] メソッドを持ち、それを含むオブジェクトは iterable iterable とみなされます。
ここに画像の説明を挿入します

これを理解すると、forEach が実際にはイテレータであることがわかります。forEach と for ループの本質的な違いは、forEach は反復可能なオブジェクト (配列セット マップ) の走査を担当するのに対し、for ループはループ メカニズムであることです。配列を通過し、配列の外に出ます。

イテレータとは正確には何なのかについて話しましょう。前に述べた Generator ジェネレータを思い出してください。呼び出されると、Iterator オブジェクトが生成されます。これには、オブジェクト {value:value,done:Boolean} を返す .next() メソッドがあります。 , value は yield 後の戻り値を返します。yield が終了すると、done が true になり、継続的な呼び出しと連続した反復を通じて内部値にアクセスします。

イテレータは特別な種類のオブジェクトです。ES6 仕様でのシンボルは返されたオブジェクトの next() メソッドであり、反復動作は完了で判断されます。イテレータは、内部表現を公開せずにトラバーサルを実装します。コードを見てください

let arr = [1, 2, 3, 4]  // 可迭代对象
let iterator = arr[Symbol.iterator]()  // 调用 Symbol.iterator 后生成了迭代器对象
console.log(iterator.next()); // {value: 1, done: false}  访问迭代器对象的next方法
console.log(iterator.next()); // {value: 2, done: false}
console.log(iterator.next()); // {value: 3, done: false}
console.log(iterator.next()); // {value: 4, done: false}
console.log(iterator.next()); // {value: undefined, done: true}

それはわかります。反復可能なオブジェクトである限り、内部の Symbol.iterator を呼び出すことで反復子が提供され、その反復子が返す next メソッドに従って内部にアクセスすることが for...of の実装原理でもあります。

let arr = [1, 2, 3, 4]
for (const item of arr) {
    
    
    console.log(item); // 1 2 3 4 
}

次のメソッドを呼び出してオブジェクトの値を返し、値が未定義になるまでそれを item に保存し、ループから抜け出します。すべての反復可能なオブジェクトは、for...of によって使用できます。他の反復​​可能なオブジェクトを見てみましょう。

function num(params) {
    
    
    console.log(arguments); // Arguments(6) [1, 2, 3, 4, callee: ƒ, Symbol(Symbol.iterator): ƒ]
    let iterator = arguments[Symbol.iterator]()
    console.log(iterator.next()); // {value: 1, done: false}
    console.log(iterator.next()); // {value: 2, done: false}
    console.log(iterator.next()); // {value: 3, done: false}
    console.log(iterator.next()); // {value: 4, done: false}
    console.log(iterator.next()); // {value: undefined, done: true}
}
num(1, 2, 3, 4)

let set = new Set('1234')
set.forEach(item => {
    
    
    console.log(item); // 1 2 3 4
})
let iterator = set[Symbol.iterator]()
console.log(iterator.next()); // {value: 1, done: false}
console.log(iterator.next()); // {value: 2, done: false}
console.log(iterator.next()); // {value: 3, done: false}
console.log(iterator.next()); // {value: 4, done: false}
console.log(iterator.next()); // {value: undefined, done: true}

そのため、反復可能オブジェクトの Symbol.iterator プロパティが呼び出されたときに反復子を生成でき、forEach も反復子を生成し、各要素の値を内部コールバック関数に渡すことも直感的にわかります。

for ループと forEach の構文の違い

本質的な違いは理解できたので、応用時の文法上の違いは何でしょうか?

  1. forEach のパラメータ。
  2. forEach の中断。
  3. forEach は独自の要素を削除するため、インデックスをリセットできません。
  4. for ループはループの開始点を制御します。

forEach のパラメータ
forEach の完全なパラメータ受け渡し内容を本当に理解していますか? それは次のようになります:

arr.forEach((self,index,arr) =>{
    
    },this)

self: 現在検索されている配列の要素デフォルトでは、配列要素は左から右に取得されます。
Index: 配列の現在の要素のインデックス、最初の要素のインデックスは 0 など。

arr: 現在走査されている配列。

this: これはコールバック関数を指します。

let arr = [1, 2, 3, 4];
let person = {
    
    
    name: '1111111111111'
};
arr.forEach(function (self, index, arr) {
    
    
    console.log(`当前元素为${
    
    self}索引为${
    
    index},属于数组${
    
    arr}`);
    console.log(this.name+='真帅');
}, person)

arr を使用して配列の重複排除を実装できます。

let arr1 = [1, 2, 1, 3, 1];
let arr2 = [];
arr1.forEach(function (self, index, arr) {
    
    
    arr.indexOf(self) === index ? arr2.push(self) : null;
});
console.log(arr2);   // [1,2,3]

ここに画像の説明を挿入します
forEachの中断

js では、関数を中断したり、ループから抜け出すために、break return continue があります。for ループでいくつかの割り込み動作を使用します。これは、配列のトラバーサルと検索の最適化に適しています。ただし、forEach はイテレータであるため、走査は順番に完了するため、上記の中断動作はサポートされていません。

let arr = [1, 2, 3, 4],
    i = 0,
    length = arr.length;
for (; i < length; i++) {
    
    
    console.log(arr[i]); //1,2
    if (arr[i] === 2) {
    
    
        break;
    };
};

arr.forEach((self,index) => {
    
    
    console.log(self);
    if (self === 2) {
    
    
        break; //报错
    };
});

arr.forEach((self,index) => {
    
    
    console.log(self);
    if (self === 2) {
    
    
        continue; //报错
    };
});

forEach のループから抜け出す必要がある場合はどうすればよいですか? 実際、try/catch を使用する方法があります。

try {
    
    
    var arr = [1, 2, 3, 4];
    arr.forEach(function (item, index) {
    
    
        //跳出条件
        if (item === 3) {
    
    
            throw new Error("LoopTerminates");
        }
        //do something
        console.log(item);
    });
} catch (e) {
    
    
    if (e.message !== "LoopTerminates") throw e;
};

リターンが発生した場合、エラーは報告されませんが、有効にはなりません。

let arr = [1, 2, 3, 4];

function find(array, num) {
    
       
    array.forEach((self, index) => {
    
           
        if (self === num) {
    
         
            return index;      
        };   
    });
};
let index = find(arr, 2);// undefined

forEach は独自の要素を削除するため、インデックスをリセットできません。

forEach では、インデックスの値を制御することができず、配列の長さを超えてループから抜け出すまで、何も考えずに増加するだけです。したがって、それ自体を削除してインデックスをリセットすることは不可能です。簡単な例を見てみましょう:

let arr = [1,2,3,4]
arr.forEach((item, index) => {
    
      
    console.log(item); // 1 2 3 4  
    index++;
});

関数本体内でインデックスが増減しても、インデックスは変わりません。実際の開発では、配列の走査と項目の削除を同時に行うことがよくありますので、forEach を使用して削除する場合は注意してください。

for ループはループの開始点を制御できます。

上で述べたように、forEach ループの開始点は人間の介入がなければ 0 しかありませんが、for ループは異なります。

let arr = [1, 2, 3, 4],
    i = 1,
    length = arr.length;

for (; i < length; i++) {
    
    
    console.log(arr[i]) // 2 3 4
};

次に、前の配列の走査と削除の操作は次のように記述できます。

let arr = [1, 2, 1],
    i = 0,
    length = arr.length;

for (; i < length; i++) {
    
    
    // 删除数组中所有的1
    if (arr[i] === 1) {
    
    
        arr.splice(i, 1);
        //重置i,否则i会跳一位
        i--;
    };
};
console.log(arr); // [2]
//等价于
var arr1 = arr.filter(index => index !== 1);
console.log(arr1) // [2]

for ループと forEach のパフォーマンスの違い

パフォーマンスの比較の観点から、フィルターと同様に新しい配列を生成するマップ反復子を追加します。ブラウザ環境での forEach マップのパフォーマンスを比較してみましょう。

パフォーマンスの比較: chrome 62 および Node.js v9.1.0 環境での for > forEach > Map: for ループは forEach より 1 倍高速で、forEach は Map より約 20% 高速です。

理由分析: for ループには追加の関数呼び出しスタックとコンテキストがないため、実装が最も単純です。forEach: ForEach の関数シグネチャにはパラメーターとコンテキストが含まれるため、パフォーマンスは for ループよりも低くなります。マップ: マップが最も遅い理由は、マップが新しい配列を返すためです。配列の作成と割り当てによってメモリ領域が割り当てられ、パフォーマンスに大きなオーバーヘッドが発生します。

マップをループ内にネストすると、不必要なメモリ消費が増加します。イテレータを使用して配列を走査する場合、新しい配列を返す必要がない場合にマップを使用することは、元の設計意図に反します。私がフロントエンド開発に取り組んでいたとき、多くの人が配列を走査するためだけにマップを使用しているのを見ました。

let data = [];
let data2 = [1,2,3];
data2.map(item=>data.push(item));

おすすめ

転載: blog.csdn.net/weixin_43228814/article/details/132922320