2 // ------------------------------------------------------------------------- //
\r
3 // ------------ local variables -------------------------------------------- //
\r
4 // ------------------------------------------------------------------------- //
\r
7 X_Callback_LIVE_LIST = [],
\r
9 X_Callback_POOL_LIST = [],
\r
11 X_Closure_COMMAND_BACK = X_Callback_LIVE_LIST,
\r
13 X_Closure_COMMAND_DROP = X_Callback_POOL_LIST,
\r
16 X_Callback_THIS_FUNC = 1,
\r
18 X_Callback_HANDLEEVENT = 2,
\r
20 X_Callback_FUNC_ONLY = 3,
\r
23 X_Callback_NONE = 0,
\r
25 X_Callback_UN_LISTEN = 1,
\r
27 X_Callback_STOP_PROPAGATION = 2,
\r
29 X_Callback_STOP_NOW = 4 | 2, // 同一階層のリスナーのキャンセル(上位へもキャンセル)
\r
31 X_Callback_PREVENT_DEFAULT = 8, // 結果動作のキャンセル,
\r
33 X_Callback_MONOPOLY = 16, // move event を独占する
\r
35 X_Callback_CAPTURE_POINTER = 16,
\r
36 X_Callback_RELEASE_POINTER = 32,
\r
39 X_Callback_SYS_CANCEL = 64 | 4 | 2;
\r
42 * handleEvent という関数のメンバーを持つオブジェクト
\r
43 * @typedef {{ handleEvent : function }}
\r
48 * <h4>クロージャについて</h4>
\r
49 * javascript 開発で多用されるクロージャですが、次の理由によりその使用は慎重になるべきです。
\r
51 * <li>正しく参照を切ってガベージコレクトされる条件を満たしたか?プログラマが見落としやすく、結果メモリリークを起こしやすい。特にコードがコールバック地獄になると目も当てられない。
\r
54 * とくに次のようなページでは、クロージャの使用を極力避けるべきです。破棄が行われることが確実で即時関数によって新しい名前を追加したくない場合と、次項で紹介する絶対に必要なケース以外で使わないようにします。
\r
56 * <li>ajax によりデータ取得がたびたび起こり、合わせて不要なデータの破棄も行う。
\r
57 * <li>シングルページアプリケーションで画面の生成と破棄が繰り返される。
\r
58 * <li>Web アプリケーションで、一度開いたら一日中操作し続けるユーザーも想定される。
\r
61 * <h4>クロージャが絶対に必要な場合</h4>
\r
62 * IE5 ~ IE8 の独自イベントモデルに於いて、イベントオブジェクトに event.currentTarget に相当するものがなく、現在どの HTML 要素でイベントが起こっているか分かりません。<br>
\r
63 * そのため HTML 要素とコールバック関数を束縛するクロージャを使う必要があります。<br>
\r
64 * 参照:『Javascript 第5版』 オライリー p430 17.3.6 attachEvent() と this キーワード<br>
\r
66 * このほかに IE8 以下と Opera11 以下の XHR ではイベントオブジェクトが用意されないため、eventType が分からない問題があります。このために eventType とコールバック関数を束縛するクロージャが必要です。<br>
\r
68 * このように、Web ブラウザと javascript の接点では、どうしてもクロージャが必要なケースがあります。<br>
\r
69 * さて、クロージャの使用を最小限に留め、残った僅かなクロージャのメモリリークをチェックし、といくら慎重に開発を行っても、そもそもクロージャが破棄されるのか?ガベージコレクションの怪しいブラウザもあり問題はまだ続きます。
\r
71 * <h4>再利用可能なクロージャ</h4>
\r
72 * クロージャの使用を最小限にしたうえで、なおかつクロージャがガベージコレクションされない可能性を考慮して、pettanR フレームワークでは再利用可能なクロージャを用意します。<br>
\r
73 * 再利用可能クロージャはフレームワーク内で生成・破棄されるため、ユーザーが直接触ることはありません。しかし debug ツールのコールスタックには登場するため、知識を持っておくことは有益です。<br>
\r
75 * <h5>再利用可能クロージャの作成</h5>
\r
76 * X_Callback_create() で再利用可能なクロージャの作成。次のパターンで呼び出します。<br>
\r
77 * 最大で三つの引数を並べる一連のパターンは、 EventDispatcher.listen unlisten, listening や X.Timer.add, once でも使われますので、ここでよく目を通しておきます。
\r
80 * <tr><th>this コンテキスト+関数<td>X_Callback_create( thisObject, func )<td>func.call( thisObject );
\r
81 * <tr><th>this コンテキスト+関数+追加引数<td>X_Callback_create( thisObject, func, [ arg1, ...args ] )<td>func.apply( thisObject, [ arg1, ...args ] );
\r
82 * <tr><th>listener オブジェクト<td>X_Callback_create( listener )<td>listener.handleEvent(); コールバックに関数でなく handleEvent 関数をメンバに持つオブジェクトを渡すのは NN4 からある javascript のお約束です。
\r
83 * <tr><th>listener オブジェクト+追加引数<td>X_Callback_create( listener, [ arg1, ...args ] )<td>listener.handleEvent.apply( listener, [ arg1, ...args ] );
\r
84 * <tr><th>関数<td>X_Callback_create( func )<td>特別な操作は不要なので再利用可能クロージャは作られません。func をそのまま利用します。
\r
85 * <tr><th>関数+引数<td>X_Callback_create( func, [ arg1, ...args ] )<td>func.apply( ?, [ arg1, ...args ] );
\r
88 * <h5>再利用可能クロージャの破棄と再利用</h5>
\r
89 * X_Callback_correct() によってクロージャは回収され再利用に備えます。<br>
\r
90 * 実は、クロージャが束縛するのは、this コンテキストやコールバック関数といった、<strong>そのもの</strong>ではなく、それらを一定のルールで格納したハッシュです。<br>
\r
91 * このハッシュはクロージャに与えた後も、適宜に取得が可能です。このハッシュのメンバーを書き換えることで、クロージャの this コンテキストやコールバック関数を書き換えています。
\r
93 * @class __CallbackHash__
\r
94 * @classdesc コールバック関数に this コンテキストや、追加の引数を設定するための情報を収めたハッシュです。<br>
\r
95 * フレームワークユーザは直接触ることにはないが、重要な情報なので書いておきます。
\r
98 var __CallbackHash__ =
\r
99 /** @lends __CallbackHash__.prototype */
\r
102 * コールバックの種類を表す数値。 this + function, this.handleEvent, function only がある。
\r
105 kind : X_Callback_THIS_FUNC,
\r
108 * @type {funciton|undefined}
\r
112 * コールバックの this コンテキスト。
\r
113 * @type {listener|object|undefined}
\r
115 context : undefined,
\r
117 * コールバックに追加する引数。イベントのコールバックでは event オブジェクトのあとに追加されるため supplement[0] が第一引数にならない点に注意。
\r
118 * @type {Array|undefined}
\r
120 supplement : undefined,
\r
122 * __CallbackHash__ の情報を元に、コールバックを実施するプロキシ。
\r
125 proxy : X_Callback_proxyCallback
\r
129 * X.Timer と X.EventDispatcher からのコールバックの返り値を定義。
\r
130 * @namespace X.Callback
\r
131 * @alias X.Callback
\r
133 X[ 'Callback' ] = {
\r
135 * このコールバックでは返り値による操作は無い。
\r
137 'NONE' : X_Callback_NONE,
\r
139 * X.Timer, X.EventDispatcher のコールバックでタイマーやイベントリスナの解除に使用。
\r
141 'UN_LISTEN' : X_Callback_UN_LISTEN,
\r
143 * 上位階層へのイベント伝播のキャンセル。DOM イベントのコールバックの戻り値に指定すると e.stopPropagation() が呼ばれる。
\r
145 'STOP_PROPAGATION' : X_Callback_STOP_PROPAGATION,
\r
147 * 以降のイベントのディスパッチを中断する。STOP_PROPAGATION との違いは、次に控えているコールバックもキャンセルされる点。但し system によって追加されたイベントはキャンセルされない。
\r
149 'STOP_NOW' : X_Callback_STOP_NOW,
\r
151 * DOM イベントのコールバックの戻り値に指定すると e.preventDefault() が呼ばれる。
\r
152 * またフレームワーク内で定義されたデフォルト動作の回避にも使用される。
\r
154 'PREVENT_DEFAULT' : X_Callback_PREVENT_DEFAULT,
\r
156 * X.UI に於いて、ポインターイベントの戻り値に指定すると、以降のポインターベントを独占する。
\r
158 'MONOPOLY' : X_Callback_MONOPOLY,
\r
160 'CAPTURE_POINTER' : X_Callback_CAPTURE_POINTER,
\r
162 'RELEASE_POINTER' : X_Callback_RELEASE_POINTER
\r
165 // ------------------------------------------------------------------------- //
\r
166 // --- implements ---------------------------------------------------------- //
\r
167 // ------------------------------------------------------------------------- //
\r
169 function X_Callback_create( thisObject, opt_callback, opt_args /* [ listener || ( context + function ) || function ][ args... ] */ ){
\r
170 var obj = X_Callback_classifyCallbackArgs( thisObject, opt_callback, opt_args ),
\r
173 if( !obj.kind ) return obj;
\r
175 if( l = X_Callback_POOL_LIST.length ){
\r
176 ret = X_Callback_POOL_LIST[ l - 1 ]; --X_Callback_POOL_LIST.length; // ret = X_Callback_POOL_LIST.pop();
\r
177 _obj = ret( X_Closure_COMMAND_BACK );
\r
179 _obj.kind = obj.kind;
\r
180 _obj.func = obj.func;
\r
181 _obj.context = obj.context;
\r
182 _obj.supplement = obj.supplement;
\r
183 _obj.proxy = X_Callback_proxyCallback;
\r
185 ret = X_Callback_actualClosure( obj );
\r
186 obj.proxy = X_Callback_proxyCallback;
\r
188 X_Callback_LIVE_LIST[ X_Callback_LIVE_LIST.length ] = ret;
\r
193 function X_Callback_classifyCallbackArgs( arg1, arg2, arg3, alt_context ){
\r
196 if( arg1 && X_Type_isFunction( arg2 ) ){
\r
197 obj = { context : arg1, func : arg2, kind : X_Callback_THIS_FUNC };
\r
199 if( arg1 && X_Type_isFunction( arg1[ 'handleEvent' ] ) ){
\r
200 obj = { context : arg1, kind : X_Callback_HANDLEEVENT };
\r
203 if( X_Type_isFunction( arg1 ) ){
\r
206 obj = { context : alt_context, func : arg1, kind : X_Callback_THIS_FUNC };
\r
208 obj = { func : arg1, kind : X_Callback_FUNC_ONLY };
\r
211 if( X_Type_isFunction( arg2 ) ){
\r
212 //console.log( 'X_Callback_classifyCallbackArgs : arg1 が ' + arg1 + 'です' ); ie4 で error
\r
214 obj = { context : alt_context, func : arg2, kind : X_Callback_THIS_FUNC };
\r
216 obj = { func : arg2, kind : X_Callback_FUNC_ONLY };
\r
220 obj = { context : alt_context, kind : X_Callback_HANDLEEVENT };
\r
223 console.log( '不正 ' + arg1 );
\r
224 console.dir( arg1 );
\r
228 if( X_Type_isArray( arg3 )){
\r
229 obj.supplement = arg3;
\r
231 return ( obj.context || obj.supplement ) ? obj : arg1;
\r
234 function X_Callback_actualClosure( obj ){
\r
236 if( arguments[ 0 ] === X_Closure_COMMAND_BACK ) return obj;
\r
237 if( arguments[ 0 ] !== X_Closure_COMMAND_DROP ) return obj.proxy( obj, arguments );
\r
241 function X_Callback_proxyCallback( xfunc, _args ){
\r
242 var args = _args || [],
\r
243 thisObj = xfunc.context,
\r
245 supp = xfunc.supplement,
\r
248 if( supp && supp.length ){
\r
252 args.length === 1 ?
\r
253 ( temp[ 0 ] = args[ 0 ] ) :
\r
254 temp.push.apply( temp, args )
\r
256 supp.length === 1 ?
\r
257 ( temp[ temp.length ] = supp[ 0 ] ) :
\r
258 temp.push.apply( temp, supp );
\r
262 switch( xfunc.kind ){
\r
264 case X_Callback_THIS_FUNC :
\r
265 return args.length === 0 ? func.call( thisObj ) : func.apply( thisObj, args );
\r
267 case X_Callback_HANDLEEVENT :
\r
268 temp = thisObj[ 'handleEvent' ];
\r
269 if( X_Type_isFunction( temp ) ){
\r
270 return args.length === 0 ? thisObj[ 'handleEvent' ]() :
\r
271 args.length === 1 ? thisObj[ 'handleEvent' ]( args[ 0 ] ) : temp.apply( thisObj, args );
\r
275 if( temp !== func && X_Type_isFunction( temp ) ){
\r
276 return args.length === 0 ? thisObj[ 'handleEvent' ]() : temp.apply( thisObj, args );
\r
278 if( X_Type_isFunction( thisObj ) ){
\r
279 return args.length === 0 ? thisObj.call( thisObj ) : thisObj.apply( thisObj, args );
\r
281 return args.length === 0 ? func.call( thisObj ) : func.apply( thisObj, args );*/
\r
283 case X_Callback_FUNC_ONLY :
\r
284 return args.length === 0 ?
\r
286 args.length === 1 ?
\r
287 func( args[ 0 ] ) :
\r
288 func.apply( null, args );
\r
290 return X_Callback_NONE;
\r
293 function X_Callback_correct( f ){
\r
294 var i = X_Callback_LIVE_LIST.indexOf( f ),
\r
297 X_Callback_LIVE_LIST.splice( i, 1 );
\r
298 X_Callback_POOL_LIST[ X_Callback_POOL_LIST.length ] = f;
\r
299 obj = f( X_Closure_COMMAND_BACK );
\r
301 if( obj.func ) delete obj.func;
\r
302 if( obj.context ) delete obj.context;
\r
303 if( obj.supplement ) delete obj.supplement;
\r
310 function X_Callback_monitor(){
\r
312 'Callback:Live' : X_Callback_LIVE_LIST.length,
\r
313 'Callback:Pool' : X_Callback_POOL_LIST.length
\r
316 function X_Callback_gc(){
\r
317 X_Callback_POOL_LIST.length = 0; // ?
\r
320 X_TEMP.onSystemReady.push( function( sys ){
\r
321 sys.monitor( X_Callback_monitor );
\r
322 sys.gc( X_Callback_gc );
\r
326 console.log( 'X.Core.Callback' );
\r