Caution
お使いのブラウザはJavaScriptが実行できない状態になっております。
当サイトはWebプログラミングの情報サイトの為、
JavaScriptが実行できない環境では正しいコンテンツが提供出来ません。
JavaScriptが実行可能な状態でご閲覧頂くようお願い申し上げます。
JavaScript
応用編
- トップページ
- JavaScript応用編 - call()とaplly()の使い方
call()とaplly()の使い方
みなさまどうも。
今回は『call()』と『apply()』について色々とやっていきましょう。これは元となるオブジェクトを変更しつつ関数オブジェクトを呼び出すメソッドになり、噛み砕いて言うと前回の記事で解説した『this』の参照先を無理やり変えるような挙動になりますね。
使用頻度はあんまり高くないですが、「とあるオブジェクトのメソッドやプロパティを別のオブジェクトで使用したい」といった場合や、「『this』の参照先を固定した状態で構築したい」といった場合に重宝します。
ではさっそくやっていきます。
まずは使い方です。『call()』と『apply()』は関数オブジェクト(Functionオブジェクト)のprototypeに用意されたメソッドなので、最初に関数を適当に定義しましょう。
var f = function(){ console.log(this); };
そしたらその関数に『.call()』、または『.apply()』をくっつけます。
var f = function(){ console.log(this); }; f.call(); f.apply();
んでもって『call()』、または『apply()』の第1引数に『this』の参照先とさせたいオブジェクトとかを渡します。windowオブジェクトを渡す場合はこんな感じになりますね。
var f = function(){ console.log(this); }; f.call(window); // 第1引数に『windowオブジェクト』を渡すことで『this』の参照先を『windowオブジェクト』とします。 f.apply(window); // 第1引数に『windowオブジェクト』を渡すことで『this』の参照先を『windowオブジェクト』とします。
これだけで『this』の参照先を変更することができます。
というわけで色々と試してみましょう。以下のサンプルを見てください。『this』の参照先を色々と変えてみました。
var o = {}; // オブジェクト『o』を定義します。 o.f = function(){ // オブジェクト『o』にメソッド『f』を定義します。 console.log(this); // 『this』を出力します。 }; o.f(); // オブジェクト『o』が出力されます。 o.f.call(window); // 『windowオブジェクト』が出力されます。 var _o = {}; // オブジェクト『_o』を定義します。 o.f.call(_o); // オブジェクト『_o』が出力されます。
こんな感じで結構自由に構築できちゃいます。
ちょっと注意点として『call()』と『apply()』の第1引数に『null』と『undefined』を渡した場合はwindowオブジェクトが渡されたものとして処理されます。
var f = function(){ console.log(this); }; f.call(null); // 第1引数に『null』を渡すと『this』の参照先は『windowオブジェクト』となります。 f.apply(undefined); // 第1引数に『undefined』を渡すと『this』の参照先は『windowオブジェクト』となります。
エラーになりそうなもんなんですがエラーにならないので注意です。念のため覚えておいて下さい。
『call()』と『apply()』の第1引数にプリミティブ値を渡した場合はラッパーオブジェクトが渡されたものとして処理されます。
var f = function(){ console.log(this); }; f.call(1); // 第1引数に数値を渡すと『Numberオブジェクト』が渡されたものとして処理されます。 f.call("abc"); // 第1引数に文字列を渡すと『Stringオブジェクト』が渡されたものとして処理されます。 f.call(true); // 第1引数に真偽値を渡すと『Booleanオブジェクト』が渡されたものとして処理されます。
※ラッパーオブジェクトについてはこちらへどうぞ。
さて、続いて『call()』と『apply()』の違いについてみていきましょう。
どちらも第1引数に渡されたものを『this』の参照先とする、というところは同じですが引数の渡し方がちょっと違います。
関数に引数を渡しつつ『call()』を実行させる場合は、渡したい値とかを『call()』の第2引数以降の引数として『,』区切りで記述してあげればOKです。こんな感じですね。
var f = function(x, y, z){ console.log(x); console.log(y); console.log(z); console.log(this); }; f.call(window, 0, 1, 2); // 第1引数は『this』の参照先、それ以降の引数は元となる関数オブジェクトの引数になります。
関数に引数を渡しつつ『apply()』を実行させる場合は、渡したい値とかを『apply()』の第2引数に添字配列で渡します。以下のような感じですね。
var f = function(x, y, z){ console.log(x); console.log(y); console.log(z); console.log(this); }; var arr = [0, 1, 2]; // 添字配列を定義。 f.apply(window, arr); // 第1引数は『this』の参照先となり、第2引数に添字配列を渡すと、渡された添字配列の0番目から順番に元となる関数の引数になります。
『,』区切りで引数として渡すのか、それとも配列でまとめて渡すのか、という違いですね。
ほとんどの場合は『call()』だけで事足りますが、可変長引数で実装する場合などで引数を動的に生成して渡したい時なんかは『apply()』の方を使用して下さい。
※可変長引数についてはこちらへどうぞ。
さて、では続いて『call()』と『apply()』の使用パターンをいくつか紹介しておきます。
使用目的としては大きく分けて、『なりすましパターン』と『保険パターン』の2つですね。
まずは『なりすましパターン』です。『call()』と『apply()』は『this』の参照先を変える仕組み、つまり元となるオブジェクトを変更するような感じになるので自身が持っていないプロパティやメソッドでも利用できるようになります。
以下のサンプルを見て下さい。
var f = function(){ // 関数『f』を定義します。 console.log(this.name); // 自身が格納されているオブジェクトのプロパティ『name』を出力します。 }; var o = {}; // オブジェクト『o』を定義します。 o.name = "ワンパンマン"; // 関数『o』のプロパティ『name』に文字列『ワンパンマン』を入れます。 f.call(o); // 関数『f』をオブジェクト『o』として実行することで文字列『ワンパンマン』が出力できます。
上記のサンプルの関数『f』に注目です。
関数『f』にはプロパティ『name』が定義されていませんが、オブジェクト『o』として関数『f』を実行させることによって文字列『ワンパンマン』が出力できていますね。
この『なりすましパターン』で良く使用されるのは通常の添字配列に用意されているメソッドを『arguments』に対して使用するパターンです。
JavaScriptの関数内では『arguments』という添字配列っぽいオブジェクトを使用することによって自身の引数を参照することができます。
var f = function(){ for(var i = 0; i < arguments.length; ++i) console.log(arguments[i]); // 実行時の引数を全て出力します。 }; f(1, 2, 3); // 数値『1』、『2』、『3』がそれぞれ出力されます。
しかし、この『arguments』というオブジェクトは『ArrayLikeオブジェクト』と呼ばれ、通常の添字配列っぽいけど通常の添字配列じゃない、という謎なオブジェクトになっており、通常の添字配列に用意されたメソッドが使えなかったりします。
例えば添字配列には『join()』というメソッドが用意されています。これは配列に含まれる値を全て文字列として結合してくれる処理になりますが、
var a = [0, 1, 2, 3, 4, 5]; console.log(a.join()); // 文字列『0,1,2,3,4,5』が出力されます。
『ArrayLikeオブジェクト』である『arguments』には『join()』メソッドが用意されていないため、『arguments』で『join()』をするとさくっとエラーになります。
var f = function(){ console.log(arguments.join()); // 『arguments』で『join()』します。 }; f(1, 2, 3); // 『join』なんてメソッドはないぜぇ、というエラーになります。
しかし、そこで『call()』や『apply()』を使用してみましょう。『Array.prototype.join.call(arguments)』とすることで通常の添字配列に用意された『join』メソッドを『arguments』に対して使用することができるようになります。
var f = function(){ console.log(Array.prototype.join.call(arguments)); // 通常の添字配列になりすました『arguments』で『join()』します。 }; f(1, 2, 3); // 文字列『1,2,3』が出力されます。
かなり強力な手法なので是非使ってみてください。
ちょっと注意点として添字配列に用意されているメソッドは『Arrayオブジェクト』の『prototype』に定義されていますので『Array.prototype.メソッド名.call(arguments)』っていう形になるので注意です。『Array.メソッド名.call(arguments)』っていう形ではございません。
※『arguments』についての詳しい解説はこちらへどうぞ。
さらに、こちらのJavaScriptの型判定の記事で紹介した『Object.prototype.toString.call()』っていう処理があるのですが、これも『Objectオブジェクト』に用意された『toString()』メソッドを他のオブジェクトになりすまして実行してる、という形になりますね。
// JavaScriptの型判定の解説で紹介した関数です。これも一種のなりすましです。 var isType = function(x){ return (x != x)? "NaN": (x === Infinity || x === -Infinity)? "Infinity": Object.prototype.toString.call(x).slice(8, -1); };
主になりすましで使用される際の目的としては、このように元々用意されている便利なメソッドをオブジェクトの壁を越えて実行させたい、といった感じになります。
その逆に自作のメソッドとかを『call()』や『apply()』でなりすまして実行する、というのはなるべく避けた方が無難です。読みづらくなるし、アルゴリズムとしてもへんてこりんになりがちなのであんまり意味がありません。
続いて『保険パターン』の紹介です。『保険パターン』とは『this』の参照先が想定と違ってしまった可能性を考えて、念のため『call()』とか『apply()』の第1引数に『this』の参照先として想定しているオブジェクトを渡しつつ実行する、というパターンです。
なんのこっちゃ、ってお話なんですが以下のサンプルを見てみて下さい。
(function(){ })();
はい、ただの即時関数でございますね。
さて、この即時関数で『this』を出力してみましょう。こんな感じになります。
(function(){ console.log(this); // 『windowオブジェクト』が出力されます。 })();
windowオブジェクトが出力されてますね。
こんな感じでメソッドではない関数オブジェクト内の『this』はwindowオブジェクトが出力されるわけなんですが、『strictモード』(厳格モード)で実行してみるとあら不思議。
"use strict"; // 『strictモード』でJavaScriptを実行させます。 (function(){ console.log(this); // 『undefined』が出力されます。 })();
なんと『undefined』となってしまいました。
このように『strictモード』でJavaScriptを実行させた場合などを原因とする環境の違いで『this』の参照先が通常と違ってしまう場合があります。
JavaScriptを『strictモード』で構築している企業さんは結構いるので、これでは困ってしまいますね。
しかし、そんな悩みも『call()』や『apply()』を使えば即解決です。第1引数にwindowオブジェクトを渡した『call()』で即時関数を呼び出してあげましょう。
"use strict"; // 『strictモード』でJavaScriptを実行させます。 (function(){ console.log(this); // // 『windowオブジェクト』が出力されます。 }).call(window); // 即時関数を『call(window)』してあげます。 (function(){ console.log(this); // // 『windowオブジェクト』が出力されます。 }).call(this); // 関数外の『this』は『windowオブジェクト』になるので『this』を渡してしまってもOKです。『CoffeeScript』でコンパイルしたJavaScriptはこの形になりますね。
有名ライブラリとかの中身を見てみると即時関数などに『.call(window)』とか『.call(this)』とかがくっついている事が多いのですが、これはこのためだったりします。
※即時関数についてはこちらへ。
※『strictモード』についてはこちらへ。
というわけで『call()』と『apply()』の解説でした。そこまで使用されるものではありませんが上記の『なりすましパターン』と『保険パターン』はたまに出てくるので覚えておくのが吉かもですね。
続いての記事では『可変長引数』とか『arguments』についてやっていきます。ではまたお会いしましょう。
この記事は桜舞が執筆致しました。
著者が愛する小型哺乳類 |
桜舞 春人 Sakurama HarutoISDN時代から様々なコンテンツを制作しているちょっと髪の毛が心配な東京在住のプログラマー。生粋のロングスリーパーで、10時間以上睡眠を取らないと基本的に体調が悪い。好きなだけ寝れる生活を送るのが夢。ゲームとスポーツと音楽が大好き。誰か髪の毛を分けて下さい。 |
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。