JavaScriptで[] .forEach.call()は何をしますか?


135

コードスニペットをいくつか見ていたところ、空の配列にforEachが適用されたノードリストに対して関数を呼び出す複数の要素が見つかりました。

たとえば、私は次のようなものがあります:

[].forEach.call( document.querySelectorAll('a'), function(el) {
   // whatever with the current node
});

しかし、それがどのように機能するのか理解できません。誰かがforEachの前にある空の配列の動作と動作を説明できますcallか?

回答:


203

[]配列です。
この配列はまったく使用されません。

配列を使用すると、のような配列プロトタイプにアクセスできるため、ページに配置されます.forEach

これはタイピングよりも速いです Array.prototype.forEach.call(...);

次に、forEach関数を入力として受け取る関数です...

[1,2,3].forEach(function (num) { console.log(num); });

...そしてthisthis配列のように、があり、のようにlengthその部分にアクセスできるという点で)各要素に対して、次のthis[1]3つを渡します。

  1. 配列の要素
  2. 要素のインデックス(3番目の要素が渡す2
  3. 配列への参照

最後に、.call関数が持っているプロトタイプです(他の関数で呼び出される関数です)。
.call最初の引数を取りthis、通常の関数内をcall最初の引数として渡したものに置き換えます(undefinedまたは毎日のJSでnull使用するwindowか、「strict-mode」の場合は渡したものになります)。残りの引数は元の関数に渡されます。

[1, 2, 3].forEach.call(["a", "b", "c"], function (item, i, arr) {
    console.log(i + ": " + item);
});
// 0: "a"
// 1: "b"
// 2: "c"

したがって、forEach関数を呼び出す簡単な方法を作成thisし、空の配列からすべての<a>タグのリストに変更します。それぞれ<a>の順序で、提供された関数を呼び出します。

編集

論理的結論/クリーンアップ

以下は、関数型プログラミングの試みを破棄し、手動のインラインループに固執することを提案する記事へのリンクです。このソリューションはハックのようで見苦しいためです。

私はしばらくは言うだろう.forEach、その対応よりも小さいと便利です.map(transformer).filter(predicate).reduce(combiner, initialValue)のいずれかへのアクセス持ちながら、あなたが本当にやりたいすべてが外の世界(ない配列)を変更する場合、それはまだの目的を果たし、n回arr[i]かをi

モットは明らかに才能があり知識豊富な男なので、どのようにして格差に対処しますか。また、自分がやっていること/どこに向かっているのか(たまには...そして他の...)時それは頭からの学習です)?

答えは実際には非常に単純で、ボブおじさんとクロックフォード卿はどちらも見落としのためにどちらも直面するでしょう。

片付けてください

function toArray (arrLike) { // or asArray(), or array(), or *whatever*
  return [].slice.call(arrLike);
}

var checked = toArray(checkboxes).filter(isChecked);
checked.forEach(listValues);

さて、あなたが自分でこれを行う必要があるかどうか疑問に思っているなら、答えはおそらくノーかもしれません...
この正確なことは...最近の高次機能を備えたevery(?)ライブラリによって行われます...
lodash、アンダースコア、またはjQueryを使用している場合、それらはすべて、要素のセットを取得し、アクションをn回実行する方法を持っています。
もしあなたがそのようなものを使っていないなら、ぜひ、あなた自身のものを書いてください。

lib.array = (arrLike, start, end) => [].slice.call(arrLike, start, end);
lib.extend = function (subject) {
  var others = lib.array(arguments, 1);
  return others.reduce(appendKeys, subject);
};

ES6(ES2015)以降のアップデート

slice( )/ array( )/ etcヘルパーメソッドは、配列を使用するのと同じようにリストを使用したい人のために便利なだけでなく、比較的近いES6 +ブラウザーで操作できる贅沢な人のためにも役立ちます将来、または今日のバベルの「トランスパイル」では、言語機能が組み込まれているため、このタイプのことは不要になります。

function countArgs (...allArgs) {
  return allArgs.length;
}

function logArgs (...allArgs) {
  return allArgs.forEach(arg => console.log(arg));
}

function extend (subject, ...others) { /* return ... */ }


var nodeArray = [ ...nodeList1, ...nodeList2 ];

超クリーンでとても便利です。
見上げて休息スプレッド演算子を。BabelJSサイトで試してください。テックスタックが適切な場合は、バベルとビルドステップを使用して本番環境で使用します。


非配列から配列への変換を使用できないようにする正当な理由はありません... ... どこでも、同じ醜い行を貼り付けるだけでコードを混乱させないでください。


今、私はそうした場合、少し混乱した原因です:console.log(this); 私は常にウィンドウオブジェクトを取得します。そして、.callがこの値を変更すると思いました
Muhammad Saleh

.call の値変更されthisます。これがcall、forEachで使用している理由です。forEachがそのように見える場合、言うことforEach (fn) { var i = 0; var len = this.length; for (; i < length; i += 1) { fn( this[i], i, this ); }によって変更thisするforEach.call( newArrLike )ことは、その新しいオブジェクトをとして使用することを意味しますthis
Norguard、2015

2
@MuhammadSaleh .callは、this渡されたものの内部の値を変更せず、forEach内部の値を変更します。呼び出したい関数に渡されない理由がわからない場合は、JavaScriptの動作を調べてください。補足として、ここ(およびmap / filter / reduce)のみに適用可能です。2番目の引数がにあり、関数の内部を設定するか、または単にを使用します。forEach.callthis thisforEachthisforEachthisarr.forEach(fn, thisInFn)[].forEach.call(arrLike, fn, thisInFn);.bindarr.forEach(fn.bind(thisInFn));
Norguard、2015

1
@thetrysteroそれがまさにポイントです。[]配列として存在するだけなので.call.forEachメソッドを使用して、this作業を行いたいセットに変更できます。.callこれは次のようになります。function (thisArg, a, b, c) { var method = this; method(a, b, c); }ただし、内部に設定する内部ブラックマジックがthisあり、method渡したものと同じですthisArg
Norguard、2015年

1
つまり、Array.from()?
zakius

53

このquerySelectorAllメソッドはを返しますNodeList。これは配列に似ていますが、配列ではありません。したがって、forEach(配列オブジェクトがを介して継承するArray.prototype)メソッドはありません。

NodeListは配列に似ているので、配列メソッドは実際に配列で機能します。そのため、を使用して、のコンテキストでメソッドを[].forEach.call呼び出すことができます。Array.prototype.forEachNodeListyourNodeList.forEach(/*...*/)

空の配列リテラルは拡張バージョンへのショートカットにすぎないことに注意してください。

Array.prototype.forEach.call(/*...*/);

7
したがって、明確にするために、標準の配列を使用[].forEach.call(['a','b'], cb)['a','b'].forEach(cb)て日常のアプリケーションでover を使用しforEachても、独自のプロトタイプを持たない配列のような構造を反復しようとするだけの利点はありませんか?あれは正しいですか?
Matt Fletcher

2
@MattFletcher:はい、そうです。どちらも機能しますが、なぜ複雑すぎるのですか?配列自体で直接メソッドを呼び出すだけです。
James Allardice、2015年

よろしくお願いします。なぜなのか、ALDIなどでストリートクレディや印象的なおばあちゃんのためだけなのかもしれません。固執します[].forEach():)
Matt Fletcher

var section = document.querySelectorAll(".section"); section.forEach((item)=>{ console.log(item.offsetTop) }) うまくいく
daslicht

@daslichtは古いバージョンのIEにはありません!(それ行う、しかし、支持体forEachアレイ上ではなく、のNodeListオブジェクト)
Djave

21

他の回答はこのコードを非常によく説明しているので、提案を追加します。

これは、簡潔さと明確さのためにリファクタリングする必要があるコードの良い例です。を使用する代わりに、[].forEach.call()またはArray.prototype.forEach.call()これを行うたびに、それから単純な関数を作成します。

function forEach( list, callback ) {
    Array.prototype.forEach.call( list, callback );
}

これで、より複雑であいまいなコードの代わりにこの関数を呼び出すことができます。

forEach( document.querySelectorAll('a'), function( el ) {
   // whatever with the current node
});

5

それを使用してよりよく書くことができます

Array.prototype.forEach.call( document.querySelectorAll('a'), function(el) {

});

何が行われるかというdocument.querySelectorAll('a')と、配列に似たオブジェクトが返されますが、Array型から継承されません。そのため、によって返される値としてコンテキストを使用してforEachArray.prototypeオブジェクトからメソッドを呼び出しますdocument.querySelectorAll('a')


4
[].forEach.call( document.querySelectorAll('a'), function(el) {
   // whatever with the current node
});

基本的には次と同じです。

var arr = document.querySelectorAll('a');
arr.forEach(function(el) {
   // whatever with the current node
});

2

この古い質問を更新したい:

[].foreach.call()最近のブラウザで要素をループするために使用する理由はほとんどありません。document.querySelectorAll("a").foreach()直接使用できます。

NodeListオブジェクトはノードのコレクションであり、通常はNode.childNodesなどのプロパティやdocument.querySelectorAll()などのメソッドによって返されます。

NodeListは配列ではありませんが、forEach()を使用して反復できます。Array.from()を使用して実際の配列に変換することもできます。

ただし、一部の古いブラウザはNodeList.forEach()やArray.from()を実装していません。これは、Array.prototype.forEach()を使用して回避できます。このドキュメントの例を参照してください。


1

空の配列のforEachプロトタイプには、Functionオブジェクトであるプロパティがあります。(空の配列はforEach、すべてのArrayオブジェクトが持つ関数への参照を取得するための簡単な方法です。)関数オブジェクトにcallは、関数でもあるプロパティがあります。関数のcall関数を呼び出すと、指定された引数で関数が実行されます。最初の引数はthisは呼び出された関数にます。

call関数のドキュメントはここにあります。のドキュメントforEachこちらです。


1

1行追加するだけです。

NodeList.prototype.forEach = HTMLCollection.prototype.forEach = Array.prototype.forEach;

そして出来上がり!

document.querySelectorAll('a').forEach(function(el) {
  // whatever with the current node
});

楽しい :-)

警告: NodeListはグローバルクラスです。公開ライブラリを作成する場合は、この推奨事項を使用しないでください。ただし、Webサイトまたはnode.jsアプリで作業するときに自己効力感を高めるための非常に便利な方法です。


1

私がいつも使用している、素早くて汚いソリューションです。プロトタイプには触れないようにします。もちろん、これを改善する方法はたくさんありますが、アイデアは思い付きます。

const forEach = (array, callback) => {
  if (!array || !array.length || !callback) return
  for (var i = 0; i < array.length; i++) {
    callback(array[i], i);
  }
}

forEach(document.querySelectorAll('.a-class'), (item, index) => {
  console.log(`Item: ${item}, index: ${index}`);
});

0

[]常に新しい配列を返します。これは同等ですnew Array()Array、ユーザーが上書きできるのに上書きできないため、配列を返すことが保証されています[]。したがって、これはのプロトタイプを取得する安全な方法でありArray、次に説明callするように、配列のようなノードリスト(this)で関数を実行するために使用されます。

指定されたthis値と個別に提供された引数を使用して関数を呼び出します。mdn


一方で[]、実際に意志は常に配列を返す、しばらくはwindow.Arrayそうあまりにもすることができ、(すべての古いブラウザでは)何も上書きできるwindow.Array.prototype.forEachもので上書きします。どちらの場合でも、ゲッター/セッターまたはオブジェクトのシーリング/フリーズ(フリーズによって拡張性の問題が発生する)をサポートしていないブラウザーでは、安全性は保証されません。
Norguard、2015

prototypeまたはメソッドを上書きする可能性に関する追加情報を追加するには、回答を自由に編集してください:forEach
Xotic750 2015

0

Norguardは、WHAT [].forEach.call()James Allardiceが なぜそれを行うのかを説明しました。querySelectorAllがNodeListforEachメソッドを持たないを返すためです...

Chrome 51以降、Firefox 50以降、Opera 38、Safari 10などの最新のブラウザを使用している場合を除きます。

そうでない場合は、ポリフィルを追加できます。

if (window.NodeList && !NodeList.prototype.forEach) {
    NodeList.prototype.forEach = function (callback, thisArg) {
        thisArg = thisArg || window;
        for (var i = 0; i < this.length; i++) {
            callback.call(thisArg, this[i], i, this);
        }
    };
}

0

このページにはたくさんの良い情報がありますが(回答 + 回答 + コメントを参照)、最近OPと同じ質問があり、全体像を把握するには少し掘り下げました。だから、ここに短いバージョンがあります:

目標はArray、配列のようなメソッドを使用することですNodeListそれらのメソッド自体を持たないすることです。

古いパターンはFunction.call()、を介してArrayのメソッドを選択し、タイプするのがより短かったためでは[]なく、配列リテラル()を使用していましたArray.prototype

[].forEach.call(document.querySelectorAll('a'), a => {})

新しいパターン(ポストECMAScript 2015)は以下を使用することArray.from()です:

Array.from(document.querySelectorAll('a')).forEach(a => {})
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.