スポンサーリンク

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