いつも忘れるので具体的な使い方を残しておこうと思います。自分のためのメモで目新しいことは何もありません。記事の最後にapply()とcall()の引数の型がどっちが配列でどっちが可変長引数リストか忘れない覚え方を載せました。そもそも間違えないとツッコまれそうですが、もし私と同様のお悩みをお持ちの方がいらっしゃいましたら、ご一読いただけたらと思います。
参考サイト
MDN(Function.prototype.apply)
MDN(Function.prototype.call)
apply(), call()とは
JavaScriptの関数はオブジェクトであり、すべての関数がプロパティとメソッドを持っています。このデフォルトで実装されているメソッドのなかにapply()とcall()があります。
JavaScriptの関数を実行すると、呼び出し元から引数とともにthis(コンテクスト、またはコンテキスト)が暗黙的に必ず渡されます。apply()とcall()はこのthisを自由に設定できるメソッドです。
構文
- fun.apply(this, array);
- 引数:コンテクストと配列1個
- fun.call(this, arg1, arg2, arg3, …);
- 引数:コンテクストと可変長の引数
使い方
最大値/最小値を取得する
var array = [0, 6, 12, -1, 3];
// 自力で検出する場合
var max = min = array[0];
for (var i=1; i<array.length; i++) {
if (max < array[i]) max = array[i];
if (min > array[i]) min = array[i];
}
console.log(min, max); // -1 12
// Mathメソッドを使う
// 比較対象の個数が定数の場合はそのまま使える
// でも10個とか100個とか書きたくない
// ちなみに第一引数は使用されないのでnullでも問題なし
Math.min(Math, [0], [1], [2], [3], [4]);
Math.max(Math, [0], [1], [2], [3], [4]);
console.log(min, max); // -1 12
// Mathメソッドのapply()を使う
// 配列を渡すことができる
var min = Math.min.apply(Math, array);
var max = Math.max.apply(Math, array);
console.log(min, max); // -1 12
配列のようなオブジェクトに配列メソッドを使う
配列のようなオブジェクトは本物の配列ではないため、直接Arrayクラスのメソッドを使うことはできません。Arrayクラスのメソッドを使うには、Array.prototypeの配列メソッドからapply()またはcall()を呼び出し、第一引数のコンテクストに配列のようなオブジェクトを指定します。
配列のようなオブジェクト
- argumentsオブジェクト
- 関数の引数
- getElementsByClassName(“クラス名”)
- 指定したクラス名を持つ要素
- getElementsByName(“name属性”)
- 指定したname属性を持つ要素
- getElementsByTagName(“タグ名”)
- 指定したタグ名を持つ要素
- querySelectorAll(“セレクタ”)
- 指定したセレクタにマッチする要素
よく使う配列メソッド
- slice
- 配列の部分取り出し(元の配列を変更しない)
- forEach/map
- 配列の各要素に対して関数を実行
// ex. 配列のようなオブジェクトを配列に変換
// 配列のメソッドが使えるようになる
var args = Array.prototype.slice.call(arguments);
var args = [].slice.call(arguments); // 上記と同じ式
// ex. 指定したクラスを持つすべての要素のテキストを書き換える
// 上記の変換を使って配列のメソッドforEach()を呼び出す
var elems = document.getElementsByClassName("counter");
var array = Array.prototype.slice.call(elems);
array.forEach(function(elem) {
elem.textContent = "0";
});
// これをcall()メソッドを使って書き直すと以下のようになる
var elems = document.getElementsByClassName("counter");
Array.prototype.forEach.call(elems, function (elem) {
elem.textContent = "0";
});
// ex. セレクタで指定されたすべての要素のクラスを書き換える
// querySelectorAll()は便利
var elems = document.querySelectorAll(".signal");
Array.prototype.forEach.call(elems, function(elem) {
elem.classList.remove("red");
elem.classList.add("blue");
});
// ex. セレクタで指定されたすべての要素のサイズを配列にして出力する
// map()の場合もforEach()と同じように使える
var elems = document.querySelectorAll(".signal");
var size = Array.prototype.map.call(elems, function(elem) {
return {w: elem.offsetWidth, h: elem.offsetHeight};
});
// ex. 可変長の引数を使用する関数を呼び出す
function sum() {
for (var i=0, n=0; i<arguments.length; i++) {
n += arguments[i];
}
return n;
}
var answer = sum(2,4,6,8,10,12,14);
var array = [2,4,6,8,10,12,14]; // 配列で扱いたい
var answer = sum.apply(this, array); // そんなときはapply
apply()とcall()どっちを使う?
引数の型の違いがあるだけでどっちも同じように使えます。どっちを使ったらいいか迷ったら、コードがきれいに見えるほうを採用すればいいと思います。
apply()とcall()の引数の型がどっちが配列でどっちが可変長引数リストか忘れない覚え方
どっちがどっちか覚えられず悩んでいたときがありました。読み方、連想、語呂合わせ… いろいろ試しましたが効果がなく、毎回ググるしかないのかとあきらめかけていました。そもそも関数名が(apply: 適用する)、(call: 呼ぶ)とか、引数の型にぜんぜん関係ないではありませんか。単純にcallbyAry()やcallbyArg()みたいにしてくれていれば… そんなしょうもないことを考えていたある日のこと、突然天啓を授かりました。
その内容は以下の図式になります。
apply >>> arrly >>> array === 配列
これを見たらもう忘れないと思います。
以上です。