2 // ------------------------------------------------------------------------- //
\r
3 // ------------ local variables -------------------------------------------- //
\r
4 // ------------------------------------------------------------------------- //
\r
6 var X_Callback_LIVE_LIST = [],
\r
7 X_Callback_POOL_LIST = [],
\r
8 X_Closure_COMMAND_BACK = X_Callback_LIVE_LIST,
\r
9 X_Closure_COMMAND_DROP = X_Callback_POOL_LIST,
\r
11 X_Callback_THIS_FUNC = 1,
\r
12 X_Callback_HANDLEEVENT = 2,
\r
13 X_Callback_FUNC_ONLY = 3,
\r
15 X_Callback_NONE = 0,
\r
16 X_Callback_UN_LISTEN = 1,
\r
17 X_Callback_STOP_PROPAGATION = 2, // 上位階層への伝播のキャンセル
\r
18 X_Callback_STOP_NOW = 4 | 2, // 同一階層のリスナーのキャンセル(上位へもキャンセル)
\r
19 X_Callback_PREVENT_DEFAULT = 8, // 結果動作のキャンセル,
\r
20 X_Callback_MONOPOLY = 16, // move event を独占する
\r
21 X_Callback_SYS_CANCEL = 32 | 4 | 2;
\r
24 * handleEvent という関数のメンバーを持つオブジェクト
\r
25 * @typedef {{ handleEvent : function }}
\r
30 * <h4>クロージャについて</h4>
\r
31 * javascript 開発で多用されるクロージャですが、次の理由によりその使用は慎重になるべきです。
\r
33 * <li>正しく参照を切ってガベージコレクトされる条件を満たしたか?プログラマが見落としやすく、結果メモリリークを起こしやすい。特にコードがコールバック地獄になると目も当てられない。
\r
36 * とくに次のようなページでは、クロージャの使用を極力避けるべきです。破棄が行われることが確実で即時関数によって新しい名前を追加したくない場合と、次項で紹介する絶対に必要なケース以外で使わないようにします。
\r
38 * <li>ajax によりデータ取得がたびたび起こり、合わせて不要なデータの破棄も行う。
\r
39 * <li>シングルページアプリケーションで画面の生成と破棄が繰り返される。
\r
40 * <li>Web アプリケーションで、一度開いたら一日中操作し続けるユーザーも想定される。
\r
43 * <h4>クロージャが絶対に必要な場合</h4>
\r
44 * IE5 ~ IE8 の独自イベントモデルに於いて、イベントオブジェクトに event.currentTarget に相当するものがなく、現在どの HTML 要素でイベントが起こっているか分かりません。<br>
\r
45 * そのため HTML 要素とコールバック関数を束縛するクロージャを使う必要があります。<br>
\r
46 * 参照:『Javascript 第5版』 オライリー p430 17.3.6 attachEvent() と this キーワード<br>
\r
48 * このほかに IE8 以下と Opera11 以下の XHR ではイベントオブジェクトが用意されないため、eventType が分からない問題があります。このために eventType とコールバック関数を束縛するクロージャが必要です。<br>
\r
50 * このように、Web ブラウザと javascript の接点では、どうしてもクロージャが必要なケースがあります。<br>
\r
51 * さて、クロージャの使用を最小限に留め、残った僅かなクロージャのメモリリークをチェックし、といくら慎重に開発を行っても、そもそもクロージャが破棄されるのか?ガベージコレクションの怪しいブラウザもあり問題はまだ続きます。
\r
53 * <h4>再利用可能なクロージャ</h4>
\r
54 * クロージャの使用を最小限にしたうえで、なおかつクロージャがガベージコレクションされない可能性を考慮して、pettanR フレームワークでは再利用可能なクロージャを用意します。<br>
\r
55 * 再利用可能クロージャはフレームワーク内で生成・破棄されるため、ユーザーが直接触ることはありません。しかし debug ツールのコールスタックには登場するため、知識を持っておくことは有益です。<br>
\r
57 * <h5>再利用可能クロージャの作成</h5>
\r
58 * X_Callback_create() で再利用可能なクロージャの作成。次のパターンで呼び出します。<br>
\r
59 * 最大で三つの引数を並べる一連のパターンは、 EventDispatcher.listen unlisten, listening や X.Timer.add, once でも使われますので、ここでよく目を通しておきます。
\r
62 * <tr><th>this コンテキスト+関数<td>X_Callback_create( thisObject, func )<td>func.call( thisObject );
\r
63 * <tr><th>this コンテキスト+関数+追加引数<td>X_Callback_create( thisObject, func, [ arg1, ...args ] )<td>func.apply( thisObject, [ arg1, ...args ] );
\r
64 * <tr><th>listener オブジェクト<td>X_Callback_create( listener )<td>listener.handleEvent(); コールバックに関数でなく handleEvent 関数をメンバに持つオブジェクトを渡すのは NN4 からある javascript のお約束です。
\r
65 * <tr><th>listener オブジェクト+追加引数<td>X_Callback_create( listener, [ arg1, ...args ] )<td>listener.handleEvent.apply( listener, [ arg1, ...args ] );
\r
66 * <tr><th>関数<td>X_Callback_create( func )<td>特別な操作は不要なので再利用可能クロージャは作られません。func をそのまま利用します。
\r
67 * <tr><th>関数+引数<td>X_Callback_create( func, [ arg1, ...args ] )<td>func.apply( ?, [ arg1, ...args ] );
\r
70 * <h5>再利用可能クロージャの破棄と再利用</h5>
\r
71 * X_Callback_correct() によってクロージャは回収され再利用に備えます。<br>
\r
72 * 実は、クロージャが束縛するのは、this コンテキストやコールバック関数といった、<strong>そのもの</strong>ではなく、それらを一定のルールで格納したハッシュです。<br>
\r
73 * このハッシュはクロージャに与えた後も、適宜に取得が可能です。このハッシュのメンバーを書き換えることで、クロージャの this コンテキストやコールバック関数を書き換えています。
\r
75 * @class __CallbackHash__
\r
76 * @classdesc コールバック関数に this コンテキストや、追加の引数を設定するための情報を収めたハッシュです。<br>
\r
77 * フレームワークユーザは直接触ることにはないが、重要な情報なので書いておきます。
\r
80 var __CallbackHash__ =
\r
81 /** @lends __CallbackHash__.prototype */
\r
84 * コールバックの種類を表す数値。 this + function, this.handleEvent, function only がある。
\r
87 kind : X_Callback_THIS_FUNC,
\r
90 * @type {funciton|undefined}
\r
94 * コールバックの this コンテキスト。
\r
95 * @type {listener|object|undefined}
\r
97 context : undefined,
\r
99 * コールバックに追加する引数。イベントのコールバックでは event オブジェクトのあとに追加されるため supplement[0] が第一引数にならない点に注意。
\r
100 * @type {Array|undefined}
\r
102 supplement : undefined,
\r
104 * __CallbackHash__ の情報を元に、コールバックを実施するプロキシ。
\r
107 proxy : X_Callback_proxyCallback
\r
111 * X.Timer と X.EventDispatcher からのコールバックの返り値を定義。
\r
112 * @namespace X.Callback
\r
113 * @alias X.Callback
\r
117 * このコールバックでは返り値による操作は無い。
\r
119 NONE : X_Callback_NONE,
\r
121 * X.Timer.add, X.EventDispatcher.listen のコールバックでタイマーやイベントリスナの解除に使用。
\r
123 UN_LISTEN : X_Callback_UN_LISTEN,
\r
125 * イベントのバブルアップを中止する。DOM イベントのコールバックの戻り値に指定すると e.stopPropagation() が呼ばれる。
\r
127 STOP_PROPAGATION : X_Callback_STOP_PROPAGATION,
\r
129 * 以降のイベントのディスパッチを中断する。
\r
131 STOP_NOW : X_Callback_STOP_NOW,
\r
133 * DOM イベントのコールバックの戻り値に指定すると e.preventDefault() が呼ばれる。
\r
134 * またフレームワーク内で定義されたデフォルト動作の回避にも使用される。
\r
136 PREVENT_DEFAULT : X_Callback_PREVENT_DEFAULT,
\r
138 * X.UI に於いて、ポインターイベントの戻り値に指定すると、以降のポインターベントを独占する。
\r
140 MONOPOLY : X_Callback_MONOPOLY
\r
143 // ------------------------------------------------------------------------- //
\r
144 // --- implements ---------------------------------------------------------- //
\r
145 // ------------------------------------------------------------------------- //
\r
147 function X_Callback_create( thisObject, opt_callback, opt_args /* [ listener || ( context + function ) || function ][ args... ] */ ){
\r
148 var obj = X_Callback_classifyCallbackArgs( thisObject, opt_callback, opt_args ),
\r
150 if( !obj.k ) return obj;
\r
151 if( l = X_Callback_POOL_LIST.length ){
\r
152 ret = X_Callback_POOL_LIST[ l - 1 ]; --X_Callback_POOL_LIST.length; // ret = X_Callback_POOL_LIST.pop();
\r
153 _obj = ret( X_Closure_COMMAND_BACK );
\r
158 _obj._ = X_Callback_proxyCallback;
\r
160 ret = X_Callback_actualClosure( obj );
\r
161 obj._ = X_Callback_proxyCallback;
\r
163 X_Callback_LIVE_LIST[ X_Callback_LIVE_LIST.length ] = ret;
\r
168 function X_Callback_classifyCallbackArgs( arg1, arg2, arg3, alt_context ){
\r
171 if( arg1 && X.Type.isFunction( arg2 ) ){
\r
172 obj = { x : arg1, f : arg2, k : X_Callback_THIS_FUNC };
\r
174 if( arg1 && X.Type.isFunction( arg1[ 'handleEvent' ] ) ){
\r
175 obj = { x : arg1, k : X_Callback_HANDLEEVENT };
\r
178 if( X.Type.isFunction( arg1 ) ){
\r
181 obj = { x : alt_context, f : arg1, k : X_Callback_THIS_FUNC };
\r
183 obj = { f : arg1, k : X_Callback_FUNC_ONLY };
\r
186 if( X.Type.isFunction( arg2 ) ){
\r
187 //console.log( 'X_Callback_classifyCallbackArgs : arg1 が ' + arg1 + 'です' ); ie4 で error
\r
189 obj = { x : alt_context, f : arg2, k : X_Callback_THIS_FUNC };
\r
191 obj = { f : arg2, k : X_Callback_FUNC_ONLY };
\r
195 obj = { x : alt_context, k : X_Callback_HANDLEEVENT };
\r
198 console.log( '不正 ' + arg1 );
\r
199 console.dir( arg1 );
\r
203 if( X.Type.isArray( arg3 )){
\r
206 return ( obj.x || obj.s ) ? obj : arg1;
\r
209 function X_Callback_actualClosure( obj ){
\r
211 if( arguments[ 0 ] === X_Closure_COMMAND_BACK ) return obj;
\r
212 if( arguments[ 0 ] !== X_Closure_COMMAND_DROP ) return obj._( obj, arguments );
\r
216 function X_Callback_proxyCallback( xfunc, _args ){
\r
217 var args = _args || [],
\r
223 if( supp && supp.length ){
\r
227 args.length === 1 ?
\r
228 ( temp[ 0 ] = args[ 0 ] ) :
\r
229 temp.push.apply( temp, args )
\r
231 supp.length === 1 ?
\r
232 ( temp[ temp.length ] = supp[ 0 ] ) :
\r
233 temp.push.apply( temp, supp );
\r
239 case X_Callback_THIS_FUNC :
\r
240 return args.length === 0 ? func.call( thisObj ) : func.apply( thisObj, args );
\r
242 case X_Callback_HANDLEEVENT :
\r
243 temp = thisObj[ 'handleEvent' ];
\r
244 if( X.Type.isFunction( temp ) ){
\r
245 return args.length === 0 ? thisObj[ 'handleEvent' ]() :
\r
246 args.length === 1 ? thisObj[ 'handleEvent' ]( args[ 0 ] ) : temp.apply( thisObj, args );
\r
250 if( temp !== func && X.Type.isFunction( temp ) ){
\r
251 return args.length === 0 ? thisObj[ 'handleEvent' ]() : temp.apply( thisObj, args );
\r
253 if( X.Type.isFunction( thisObj ) ){
\r
254 return args.length === 0 ? thisObj.call( thisObj ) : thisObj.apply( thisObj, args );
\r
256 return args.length === 0 ? func.call( thisObj ) : func.apply( thisObj, args );*/
\r
258 case X_Callback_FUNC_ONLY :
\r
259 return args.length === 0 ?
\r
261 args.length === 1 ?
\r
262 func( args[ 0 ] ) :
\r
263 func.apply( null, args );
\r
265 return X_Callback_NONE;
\r
268 function X_Callback_correct( f ){
\r
269 var i = X_Callback_LIVE_LIST.indexOf( f ),
\r
272 X_Callback_LIVE_LIST.splice( i, 1 );
\r
273 X_Callback_POOL_LIST[ X_Callback_POOL_LIST.length ] = f;
\r
274 obj = f( X_Closure_COMMAND_BACK );
\r
276 if( obj.f ) delete obj.f;
\r
277 if( obj.x ) delete obj.x;
\r
278 if( obj.s ) delete obj.s;
\r
285 function X_Callback_monitor(){
\r
287 'Callback:Live' : X_Callback_LIVE_LIST.length,
\r
288 'Callback:Pool' : X_Callback_POOL_LIST.length
\r
291 function X_Callback_gc(){
\r
292 X_Callback_POOL_LIST.length = 0; // ?
\r
295 X_TEMP.onSystemReady.push( function( sys ){
\r
296 sys.monitor( X_Callback_monitor );
\r
297 sys.gc( X_Callback_gc );
\r
301 console.log( 'X.Core.Callback' );
\r