JavaScriptのFunction.prototype.applyを,やさしいピュアJavaScriptだけで実装してみよう (call/applyの仕組みを理解するためのオレオレ実装)
JavaScriptのcall/applyは,
コンテキスト「this」を使いこなす中級プログラミングには必須だ。
また,可変個の引数を渡したいなどの局面でも役に立つ。
もしcall/applyがないと,各種ライブラリ・フレームワークは実現不可能。
それほど重要な働きをになうメソッドだ。
このapplyの動作する基本的な仕組みを理解するために,
apply()をJavaScriptだけで実装してみると,どうなるか?
ものすごくやさしいピュアJSで書いてみた。
// Function.prototype.applyをオレオレ実装できるか /* callやapplyの中身は,本来はnative codeであり JavaScriptだけのソースコードで記述されるものではない。 下記のオレオレコードは, あくまでも概念の理解のための実験的なものです。 */ Function.prototype.oreore_apply = function( _this, arr ){ /* ポイント1: 関数の実行されるコンテキストである 「this」を無理やり変える。 ちなみに,ecmascriptに限らず たいていの言語では,thisへの代入は不可能 */ // 実行したい関数 var target_func = this; // ランダムな関数名を作る var new_func_name = "__func_" + new Date().getTime(); // コンテキストとしたいオブジェクトに, // 実行したい関数を無理やりくっつける。 _this[ new_func_name ] = target_func; // これで,コンテキストを無理やり変更できた。 /* ポイント2: 実行したい関数の引数に,任意の配列から 可変個の引数を渡したい。 hoge() にも,new Function() にも 配列を渡す機能はない。 */ // そこで最後の手であるevalに手を出す。 // // ↑まるで消費者金融に初めて手を染めるような // (または会社の金に手をつけるかのような) // 一種の罪悪感を感じるが・・・ // evalで実行したいコード var exec_code = "_this[ new_func_name ]("; // 引数を一個ずつカンマ区切りでくっつける var arr_as_str = []; for( var i = 0; i < arr.length; i ++ ){ arr_as_str.push( "arr[" + i + "]" ); } // 化石のようなコードだが,ここでは // 初等的なピュアJSのみで書こうとしているので // これでよい。 exec_code += arr_as_str.join(",") + ")"; // evalの戻り値を返却する。 // やったね! return eval( exec_code ); }; // 動作テスト // デバッグ出力用の関数 var log = ( ( ( typeof console ) !== "undefined" ) ? (function(s){ console.log(s); }) : ( ( ( typeof WScript ) !== "undefined" ) ? ( function(s){ WScript.Echo(s); }) : alert ) ); (function( a, b ){ // applyに渡した引数をthisとして, // thisのプロパティを呼べる log( "this.length = " + this.length ); // 3 log( "this.join : " + this.join("_") ); // 1_2_3 // applyの第二引数の配列も, 引数として取れている log( "a = " + a ); // 4 log( "b = " + b ); // 5 }).oreore_apply( [1, 2, 3], [4, 5] );
↑一応動く。
ブラウザ上でHTMLに貼り付けても動く。
(Firebugのconsoleに動作結果が出力される。)
あるいは,上記のコードだけをhoge.jsのようにメモ帳で保存し,
Windows上でダブルクリックしても,WSH/JScriptとして動作する。
基本的な動作は,いちおう実現できたという事になるか。
evalを使ってるけど,そこは怒らないで我慢してもらえると嬉しい。
ただし本物のcall/applyには,ブラウザ依存で細かい不思議な挙動がある。
そういった細かい点までは再現できていないので念のため注意。
javascript:alert([].sort.call(null)) == [object window] の謎 - Togetterまとめ
http://togetter.com/li/6099
- alert([].sort.call(null)) →これで window オブジェクトが取れる
参考
callとapplyの違いを一発で覚える記憶法がある。
JavaScriptのcallとapplyの違いを,一発で記憶して忘れない方法(メソッド引数が個別なのか配列なのかの違いを暗記する方法)
http://computer-technology.hateblo.jp/entry/20141221/p1
- JavaScriptのcall/applyの違いには,覚え方がある。
- call : 「コ」ール: 「個」別に引数を渡す。
- apply : 「ア」プライ: 「ア」レイとして引数を渡す。
- 「第二引数として,個別に渡すか,配列で渡すかの違いだ。 上記のように,「コ」と「ア」の区別で記憶しておけば忘れない」
JavaScriptの動かないコード(中級編)callで,コンテキスト引数にnull・undefinedや,falseなどプリミティブ型の値を渡した時のthisの挙動
http://language-and-engineering.hatenablog.jp/entry/20141226/JavaScriptCallMe...
JavaScriptの動かないコード (中級編) evalでfunctionを作る時のエラー
http://language-and-engineering.hatenablog.jp/entry/20080919/1221761313
JavaScriptの動かないコード (中級編) jsonオブジェクトをevalできないエラー
http://language-and-engineering.hatenablog.jp/entry/20081022/1224597688
JavaScriptで,グローバル変数の存在判定をする3つの方法 ("window"の定義状況を確認したい)
http://language-and-engineering.hatenablog.jp/entry/20090412/p1
alert() と書くために,わざわざ行頭に戻らなくてもすむ方法
http://language-and-engineering.hatenablog.jp/entry/20100829/p1