X-Git-Url: http://git.osdn.jp/view?a=blobdiff_plain;f=0.6.x%2Fjs%2F01_core%2F10_XCallback.js;h=f042a01832069512e5e84f9626dbed87dc7b6c0f;hb=9e30a8480de03f0b36d2411a0ecb7f1d0f47b61f;hp=a57ce6cffa186bdb7e1c326120457d75f9174e0a;hpb=36e3d975f201a804af90f1a21ff352d14a438f6c;p=pettanr%2FclientJs.git diff --git a/0.6.x/js/01_core/10_XCallback.js b/0.6.x/js/01_core/10_XCallback.js index a57ce6c..f042a01 100644 --- a/0.6.x/js/01_core/10_XCallback.js +++ b/0.6.x/js/01_core/10_XCallback.js @@ -26,60 +26,124 @@ var X_Callback_LIVE_LIST = [], */ var listener; -/* - * コールバックに thisObject や、追加の引数を設定するための情報を収めたハッシュ - * @typedef {{ k : number, f : (function|undefined), x : (listener|undefined), s : (Array|undefined) }} - */ -var callbackHash; - -/* +/** + *

クロージャについて

+ * javascript 開発で多用されるクロージャですが、次の理由によりその使用は慎重になるべきです。 + *
    + *
  1. 正しく参照を切ってガベージコレクトされる条件を満たしたか?プログラマが見落としやすく、結果メモリリークを起こしやすい。特にコードがコールバック地獄になると目も当てられない。 + *
+ * + * とくに次のようなページでは、クロージャの使用を極力避けるべきです。破棄が行われることが確実で即時関数によって新しい名前を追加したくない場合と、次項で紹介する絶対に必要なケース以外で使わないようにします。 + *
    + *
  1. ajax によりデータ取得がたびたび起こり、合わせて不要なデータの破棄も行う。 + *
  2. シングルページアプリケーションで画面の生成と破棄が繰り返される。 + *
  3. Web アプリケーションで、一度開いたら一日中操作し続けるユーザーも想定される。 + *
+ * + *

クロージャが絶対に必要な場合

+ * IE5 ~ IE8 の独自イベントモデルに於いて、イベントオブジェクトに event.currentTarget に相当するものがなく、現在どの HTML 要素でイベントが起こっているか分かりません。
+ * そのため HTML 要素とコールバック関数を束縛するクロージャを使う必要があります。
+ * 参照:『Javascript 第5版』 オライリー p430 17.3.6 attachEvent() と this キーワード
+ * + * このほかに IE8 以下と Opera11 以下の XHR ではイベントオブジェクトが用意されないため、eventType が分からない問題があります。このために eventType とコールバック関数を束縛するクロージャが必要です。
+ * + * このように、Web ブラウザと javascript の接点では、どうしてもクロージャが必要なケースがあります。
+ * さて、クロージャの使用を最小限に留め、残った僅かなクロージャのメモリリークをチェックし、といくら慎重に開発を行っても、そもそもクロージャが破棄されるのか?ガベージコレクションの怪しいブラウザもあり問題はまだ続きます。 + * + *

再利用可能なクロージャ

+ * クロージャの使用を最小限にしたうえで、なおかつクロージャがガベージコレクションされない可能性を考慮して、pettanR フレームワークでは再利用可能なクロージャを用意します。
+ * 再利用可能クロージャはフレームワーク内で生成・破棄されるため、ユーザーが直接触ることはありません。しかし debug ツールのコールスタックには登場するため、知識を持っておくことは有益です。
+ * + *
再利用可能クロージャの作成
+ * X_Callback_create() で再利用可能なクロージャの作成。次のパターンで呼び出します。
+ * 最大で三つの引数を並べる一連のパターンは、 EventDispatcher.listen unlisten, listening や X.Timer.add, once でも使われますので、ここでよく目を通しておきます。 + * + * + *
this コンテキスト+関数X_Callback_create( thisObject, func )func.call( thisObject ); + *
this コンテキスト+関数+追加引数X_Callback_create( thisObject, func, [ arg1, ...args ] )func.apply( thisObject, [ arg1, ...args ] ); + *
listener オブジェクトX_Callback_create( listener )listener.handleEvent(); コールバックに関数でなく handleEvent 関数をメンバに持つオブジェクトを渡すのは NN4 からある javascript のお約束です。 + *
listener オブジェクト+追加引数X_Callback_create( listener, [ arg1, ...args ] )listener.handleEvent.apply( listener, [ arg1, ...args ] ); + *
関数X_Callback_create( func )特別な操作は不要なので再利用可能クロージャは作られません。func をそのまま利用します。 + *
関数+引数X_Callback_create( func, [ arg1, ...args ] )func.apply( ?, [ arg1, ...args ] ); + *
* - * @typedef {(funciton|{ _ : function, same : function, kill : function, a : (Array|undefined) })} + *
再利用可能クロージャの破棄と再利用
+ * X_Callback_correct() によってクロージャは回収され再利用に備えます。
+ * 実は、クロージャが束縛するのは、this コンテキストやコールバック関数といった、そのものではなく、それらを一定のルールで格納したハッシュです。
+ * このハッシュはクロージャに与えた後も、適宜に取得が可能です。このハッシュのメンバーを書き換えることで、クロージャの this コンテキストやコールバック関数を書き換えています。 + * + * @class __CallbackHash__ + * @classdesc コールバック関数に this コンテキストや、追加の引数を設定するための情報を収めたハッシュです。
+ * フレームワークユーザは直接触ることにはないが、重要な情報なので書いておきます。 + * @private */ -var functionHash; +var __CallbackHash__ = +/** @lends __CallbackHash__.prototype */ +{ + /** + * コールバックの種類を表す数値。 this + function, this.handleEvent, function only がある。 + * @type {number} + */ + kind : X_Callback_THIS_FUNC, + /** + * コールバック。 + * @type {funciton|undefined} + */ + func : undefined, + /** + * コールバックの this コンテキスト。 + * @type {listener|object|undefined} + */ + context : undefined, + /** + * コールバックに追加する引数。イベントのコールバックでは event オブジェクトのあとに追加されるため supplement[0] が第一引数にならない点に注意。 + * @type {Array|undefined} + */ + supplement : undefined, + /** + * __CallbackHash__ の情報を元に、コールバックを実施するプロキシ。 + * @type {Function} + */ + proxy : X_Callback_proxyCallback +}; +/** + * X.Timer と X.EventDispatcher からのコールバックの返り値を定義。 + * @namespace X.Callback + * @alias X.Callback + */ X.Callback = { - + /** + * このコールバックでは返り値による操作は無い。 + */ NONE : X_Callback_NONE, + /** + * X.Timer.add, X.EventDispatcher.listen のコールバックでタイマーやイベントリスナの解除に使用。 + */ UN_LISTEN : X_Callback_UN_LISTEN, + /** + * イベントのバブルアップを中止する。DOM イベントのコールバックの戻り値に指定すると e.stopPropagation() が呼ばれる。 + */ STOP_PROPAGATION : X_Callback_STOP_PROPAGATION, + /** + * 以降のイベントのディスパッチを中断する。 + */ STOP_NOW : X_Callback_STOP_NOW, + /** + * DOM イベントのコールバックの戻り値に指定すると e.preventDefault() が呼ばれる。 + * またフレームワーク内で定義されたデフォルト動作の回避にも使用される。 + */ PREVENT_DEFAULT : X_Callback_PREVENT_DEFAULT, - MONOPOLY : X_Callback_MONOPOLY, - - create : X_Callback_create, - - sys_monitor : function(){ - return { - 'Live callback' : X_Callback_LIVE_LIST.length, - 'Pool callback' : X_Callback_POOL_LIST.length - }; - }, - - sys_gc : function(){ - var list = X_Callback_POOL_LIST, - f; - while( 0 < list.length ){ - f = list.shift(); - X_Callback_correct( f ); - delete f[ 'kill' ]; - delete f[ 'same' ]; - }; - } + /** + * X.UI に於いて、ポインターイベントの戻り値に指定すると、以降のポインターベントを独占する。 + */ + MONOPOLY : X_Callback_MONOPOLY }; -X_TEMP.onSystemReady.push( X_Callback_handleSystemEvent ); - // ------------------------------------------------------------------------- // // --- implements ---------------------------------------------------------- // // ------------------------------------------------------------------------- // -function X_Callback_handleSystemEvent( e ){ - switch( e ){ - case '': - }; -}; - function X_Callback_create( thisObject, opt_callback, opt_args /* [ listener || ( context + function ) || function ][ args... ] */ ){ var obj = X_Callback_classifyCallbackArgs( thisObject, opt_callback, opt_args ), l, ret, _obj; @@ -91,21 +155,15 @@ function X_Callback_create( thisObject, opt_callback, opt_args /* [ listener || _obj.f = obj.f; _obj.x = obj.x; _obj.s = obj.s; + _obj._ = X_Callback_proxyCallback; } else { - ret = X_Closure_actualClosure( obj ); - ret.kill = X_Callback_kill; - ret.same = X_Callback_same; + ret = X_Callback_actualClosure( obj ); + obj._ = X_Callback_proxyCallback; }; X_Callback_LIVE_LIST[ X_Callback_LIVE_LIST.length ] = ret; - return ret; + return ret; }; -function X_Closure_actualClosure( obj ){ - return function(){ - if( arguments[ 0 ] === X_Closure_COMMAND_BACK ) return obj; - if( arguments[ 0 ] !== X_Closure_COMMAND_DROP ) return X_Callback_proxyCallback( obj, arguments ); - }; -}; function X_Callback_classifyCallbackArgs( arg1, arg2, arg3, alt_context ){ var obj; @@ -148,6 +206,13 @@ function X_Callback_classifyCallbackArgs( arg1, arg2, arg3, alt_context ){ return ( obj.x || obj.s ) ? obj : arg1; }; +function X_Callback_actualClosure( obj ){ + return function(){ + if( arguments[ 0 ] === X_Closure_COMMAND_BACK ) return obj; + if( arguments[ 0 ] !== X_Closure_COMMAND_DROP ) return obj._( obj, arguments ); + }; +}; + function X_Callback_proxyCallback( xfunc, _args ){ var args = _args || [], thisObj = xfunc.x, @@ -200,20 +265,6 @@ function X_Callback_proxyCallback( xfunc, _args ){ return X_Callback_NONE; }; -function X_Callback_same( arg1, arg2, arg3 ){ - var hash; - - if( arg1 && arg1[ 'kill' ] === X_Callback_kill ) return this === arg1; - - hash = X_Callback_classifyCallbackArgs( arg1, arg2, arg3 ); - - return hash && this.k === hash.k && this.x === hash.x && this.f === hash.f && this.s === hash.s; -}; - -function X_Callback_kill(){ - X_Callback_correct( this ); -}; - function X_Callback_correct( f ){ var i = X_Callback_LIVE_LIST.indexOf( f ), obj; @@ -225,12 +276,26 @@ function X_Callback_correct( f ){ if( obj.f ) delete obj.f; if( obj.x ) delete obj.x; if( obj.s ) delete obj.s; + delete obj._; return true; }; return false; }; +function X_Callback_monitor(){ + return { + 'Callback:Live' : X_Callback_LIVE_LIST.length, + 'Callback:Pool' : X_Callback_POOL_LIST.length + }; +}; +function X_Callback_gc(){ + X_Callback_POOL_LIST.length = 0; // ? +}; +X_TEMP.onSystemReady.push( function( sys ){ + sys.monitor( X_Callback_monitor ); + sys.gc( X_Callback_gc ); +}); console.log( 'X.Core.Callback' );