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
22 X_Callback_THIS_FUNCNAME = 4,
\r
25 X_Callback_NONE = 0,
\r
27 X_Callback_UN_LISTEN = 1,
\r
29 X_Callback_STOP_PROPAGATION = 2,
\r
31 X_Callback_STOP_NOW = 4 | 2, // 同一階層のリスナーのキャンセル(上位へもキャンセル)
\r
33 X_Callback_PREVENT_DEFAULT = 8, // 結果動作のキャンセル,
\r
35 X_Callback_CAPTURE_POINTER = 16,
\r
37 X_Callback_RELEASE_POINTER = 32,
\r
40 X_Callback_SYS_CANCEL = 64 | 4 | 2;
\r
45 * handleEvent という関数のメンバーを持つオブジェクト
\r
46 * @typedef {{ handleEvent : function }}
\r
51 * <h4>クロージャについて</h4>
\r
52 * javascript 開発で多用されるクロージャですが、次の理由によりその使用は慎重になるべきです。
\r
54 * <li>正しく参照を切ってガベージコレクトされる条件を満たしたか?プログラマが見落としやすく、結果メモリリークを起こしやすい。特にコードがコールバック地獄になると目も当てられない。
\r
57 * とくに次のようなページでは、クロージャの使用を極力避けるべきです。破棄が行われることが確実で即時関数によって新しい名前を追加したくない場合と、次項で紹介する絶対に必要なケース以外で使わないようにします。
\r
59 * <li>ajax によりデータ取得がたびたび起こり、合わせて不要なデータの破棄も行う。
\r
60 * <li>シングルページアプリケーションで画面の生成と破棄が繰り返される。
\r
61 * <li>Web アプリケーションで、一度開いたら一日中操作し続けるユーザーも想定される。
\r
64 * <h4>クロージャが絶対に必要な場合</h4>
\r
65 * IE5 ~ IE8 の独自イベントモデルに於いて、イベントオブジェクトに event.currentTarget に相当するものがなく、現在どの HTML 要素でイベントが起こっているか分かりません。<br>
\r
66 * そのため HTML 要素とコールバック関数を束縛するクロージャを使う必要があります。<br>
\r
67 * 参照:『Javascript 第5版』 オライリー p430 17.3.6 attachEvent() と this キーワード<br>
\r
69 * このほかに IE8 以下と Opera11 以下の XHR ではイベントオブジェクトが用意されないため、eventType が分からない問題があります。このために eventType とコールバック関数を束縛するクロージャが必要です。<br>
\r
71 * このように、Web ブラウザと javascript の接点では、どうしてもクロージャが必要なケースがあります。<br>
\r
72 * さて、クロージャの使用を最小限に留め、残った僅かなクロージャのメモリリークをチェックし、といくら慎重に開発を行っても、そもそもクロージャが破棄されるのか?ガベージコレクションの怪しいブラウザもあり問題はまだ続きます。
\r
74 * <h4>再利用可能なクロージャ</h4>
\r
75 * クロージャの使用を最小限にしたうえで、なおかつクロージャがガベージコレクションされない可能性を考慮して、pettanR フレームワークでは再利用可能なクロージャを用意します。<br>
\r
76 * 再利用可能クロージャはフレームワーク内で生成・破棄されるため、ユーザーが直接触ることはありません。しかし debug ツールのコールスタックには登場するため、知識を持っておくことは有益です。<br>
\r
78 * <h5>再利用可能クロージャの作成</h5>
\r
79 * X_Callback_create() で再利用可能なクロージャの作成。次のパターンで呼び出します。<br>
\r
80 * 最大で三つの引数を並べる一連のパターンは、 EventDispatcher.listen unlisten, listening や X.Timer.add, once でも使われますので、ここでよく目を通しておきます。
\r
83 * <tr><th>this コンテキスト+関数<td>X_Callback_create( thisObject, func )<td>func.call( thisObject );
\r
84 * <tr><th>this コンテキスト+関数+追加引数<td>X_Callback_create( thisObject, func, [ arg1, ...args ] )<td>func.apply( thisObject, [ arg1, ...args ] );
\r
85 * <tr><th>listener オブジェクト<td>X_Callback_create( listener )<td>listener.handleEvent(); コールバックに関数でなく handleEvent 関数をメンバに持つオブジェクトを渡すのは NN4 からある javascript のお約束です。
\r
86 * <tr><th>listener オブジェクト+追加引数<td>X_Callback_create( listener, [ arg1, ...args ] )<td>listener.handleEvent.apply( listener, [ arg1, ...args ] );
\r
87 * <tr><th>関数<td>X_Callback_create( func )<td>特別な操作は不要なので再利用可能クロージャは作られません。func をそのまま利用します。
\r
88 * <tr><th>関数+引数<td>X_Callback_create( func, [ arg1, ...args ] )<td>func.apply( ?, [ arg1, ...args ] );
\r
91 * <h5>再利用可能クロージャの破棄と再利用</h5>
\r
92 * X_Callback_correct() によってクロージャは回収され再利用に備えます。<br>
\r
93 * 実は、クロージャが束縛するのは、this コンテキストやコールバック関数といった、<strong>そのもの</strong>ではなく、それらを一定のルールで格納したハッシュです。<br>
\r
94 * このハッシュはクロージャに与えた後も、適宜に取得が可能です。このハッシュのメンバーを書き換えることで、クロージャの this コンテキストやコールバック関数を書き換えています。
\r
96 * @class __CallbackHash__
\r
97 * @classdesc コールバック関数に this コンテキストや、追加の引数を設定するための情報を収めたハッシュです。<br>
\r
98 * フレームワークユーザは直接触ることにはないが、重要な情報なので書いておきます。
\r
101 var __CallbackHash__ =
\r
102 /** @lends __CallbackHash__.prototype */
\r
105 * コールバックの種類を表す数値。 this + function, this.handleEvent, function only がある。
\r
108 kind : X_Callback_THIS_FUNC,
\r
111 * @type {funciton|undefined}
\r
115 * コールバックの this コンテキスト。
\r
116 * @type {listener|object|undefined}
\r
118 context : undefined,
\r
120 * コールバックに追加する引数。イベントのコールバックでは event オブジェクトのあとに追加されるため supplement[0] が第一引数にならない点に注意。
\r
121 * @type {Array|undefined}
\r
123 supplement : undefined,
\r
125 * __CallbackHash__ の情報を元に、コールバックを実施するプロキシ。
\r
128 proxy : X_Callback_proxyCallback
\r
132 * X.Timer と X.EventDispatcher からのコールバックの返り値を定義。
\r
133 * @namespace X.Callback
\r
134 * @alias X.Callback
\r
136 X[ 'Callback' ] = {
\r
138 * このコールバックでは返り値による操作は無い。
\r
140 'NONE' : X_Callback_NONE,
\r
142 * X.Timer, X.EventDispatcher のコールバックでタイマーやイベントリスナの解除に使用。
\r
144 'UN_LISTEN' : X_Callback_UN_LISTEN,
\r
146 * 上位階層へのイベント伝播のキャンセル。DOM イベントのコールバックの戻り値に指定すると e.stopPropagation() が呼ばれる。
\r
148 'STOP_PROPAGATION' : X_Callback_STOP_PROPAGATION,
\r
150 * 以降のイベントのディスパッチを中断する。STOP_PROPAGATION との違いは、次に控えているコールバックもキャンセルされる点。但し system によって追加されたイベントはキャンセルされない。
\r
152 'STOP_NOW' : X_Callback_STOP_NOW,
\r
154 * DOM イベントのコールバックの戻り値に指定すると e.preventDefault() が呼ばれる。
\r
155 * またフレームワーク内で定義されたデフォルト動作の回避にも使用される。
\r
157 'PREVENT_DEFAULT' : X_Callback_PREVENT_DEFAULT,
\r
160 * X.UI に於いて、ポインターイベントの戻り値に指定すると、以降のポインターベントを独占する。
\r
162 'CAPTURE_POINTER' : X_Callback_CAPTURE_POINTER,
\r
164 'RELEASE_POINTER' : X_Callback_RELEASE_POINTER
\r
167 // ------------------------------------------------------------------------- //
\r
168 // --- implements ---------------------------------------------------------- //
\r
169 // ------------------------------------------------------------------------- //
\r
171 function X_Callback_create( thisObject, opt_callback, opt_args /* [ listener || ( context + function ) || function ][ args... ] */ ){
\r
172 var obj = X_Callback_classifyCallbackArgs( thisObject, opt_callback, opt_args ),
\r
175 if( !obj.kind ) return obj;
\r
177 if( l = X_Callback_POOL_LIST.length ){
\r
178 ret = X_Callback_POOL_LIST[ l - 1 ]; --X_Callback_POOL_LIST.length; // ret = X_Callback_POOL_LIST.pop();
\r
179 _obj = ret( X_Closure_COMMAND_BACK );
\r
181 _obj.kind = obj.kind;
\r
182 _obj.name = obj.name;
\r
183 _obj.func = obj.func;
\r
184 _obj.context = obj.context;
\r
185 _obj.supplement = obj.supplement;
\r
186 _obj.proxy = X_Callback_proxyCallback;
\r
188 ret = X_Callback_actualClosure( obj );
\r
189 obj.proxy = X_Callback_proxyCallback;
\r
191 X_Callback_LIVE_LIST[ X_Callback_LIVE_LIST.length ] = ret;
\r
196 function X_Callback_classifyCallbackArgs( arg1, arg2, arg3, alt_context ){
\r
199 if( X_Type_isObject( arg1 ) && X_Type_isFunction( arg2 ) ){
\r
200 obj = { context : arg1, func : arg2, kind : X_Callback_THIS_FUNC };
\r
202 if( X_Type_isObject( arg1 ) ){
\r
203 if( arg2 && X_Type_isString( arg2 ) ){
\r
204 obj = { context : arg1, name : arg2, kind : X_Callback_THIS_FUNCNAME };
\r
206 obj = { context : arg1, kind : X_Callback_HANDLEEVENT };
\r
210 if( X_Type_isFunction( arg1 ) ){
\r
213 obj = { context : alt_context, func : arg1, kind : X_Callback_THIS_FUNC };
\r
215 obj = { func : arg1, kind : X_Callback_FUNC_ONLY };
\r
218 if( X_Type_isFunction( arg2 ) ){
\r
219 //console.log( 'X_Callback_classifyCallbackArgs : arg1 が ' + arg1 + 'です' ); ie4 で error
\r
221 obj = { context : alt_context, func : arg2, kind : X_Callback_THIS_FUNC };
\r
223 obj = { func : arg2, kind : X_Callback_FUNC_ONLY };
\r
226 if( alt_context && X_Type_isString( arg1 ) ){
\r
228 obj = { context : alt_context, name : arg1, kind : X_Callback_THIS_FUNCNAME };
\r
231 obj = { context : alt_context, kind : X_Callback_HANDLEEVENT };
\r
234 console.log( '不正 ' + arg1 );
\r
235 console.dir( arg1 );
\r
239 if( X_Type_isArray( arg3 )){
\r
240 obj.supplement = arg3;
\r
242 return ( obj.context || obj.supplement ) ? obj : arg1;
\r
245 function X_Callback_actualClosure( obj ){
\r
247 if( arguments[ 0 ] === X_Closure_COMMAND_BACK ) return obj;
\r
248 if( arguments[ 0 ] !== X_Closure_COMMAND_DROP ) return obj.proxy( obj, arguments );
\r
252 function X_Callback_proxyCallback( xfunc, _args ){
\r
253 var args = _args || [],
\r
254 thisObj = xfunc.context,
\r
256 supp = xfunc.supplement,
\r
257 temp, ret, funcName;
\r
259 if( supp && supp.length ){
\r
263 args.length === 1 ?
\r
264 ( temp[ 0 ] = args[ 0 ] ) :
\r
265 temp.push.apply( temp, args )
\r
267 supp.length === 1 ?
\r
268 ( temp[ temp.length ] = supp[ 0 ] ) :
\r
269 temp.push.apply( temp, supp );
\r
273 switch( xfunc.kind ){
\r
275 case X_Callback_THIS_FUNC :
\r
276 return args.length === 0 ? func.call( thisObj ) : func.apply( thisObj, args );
\r
278 case X_Callback_THIS_FUNCNAME :
\r
279 funcName = xfunc.name;
\r
280 case X_Callback_HANDLEEVENT :
\r
281 funcName = funcName || 'handleEvent';
\r
282 temp = thisObj[ funcName ];
\r
283 if( X_Type_isFunction( temp ) ){
\r
284 return args.length === 0 ? thisObj[ 'handleEvent' ]() :
\r
285 args.length === 1 ? thisObj[ 'handleEvent' ]( args[ 0 ] ) : temp.apply( thisObj, args );
\r
289 if( temp !== func && X_Type_isFunction( temp ) ){
\r
290 return args.length === 0 ? thisObj[ 'handleEvent' ]() : temp.apply( thisObj, args );
\r
292 if( X_Type_isFunction( thisObj ) ){
\r
293 return args.length === 0 ? thisObj.call( thisObj ) : thisObj.apply( thisObj, args );
\r
295 return args.length === 0 ? func.call( thisObj ) : func.apply( thisObj, args );*/
\r
297 case X_Callback_FUNC_ONLY :
\r
298 return args.length === 0 ?
\r
300 args.length === 1 ?
\r
301 func( args[ 0 ] ) :
\r
302 func.apply( null, args );
\r
304 return X_Callback_NONE;
\r
307 function X_Callback_correct( f ){
\r
308 var i = X_Callback_LIVE_LIST.indexOf( f ),
\r
311 X_Callback_LIVE_LIST.splice( i, 1 );
\r
312 X_Callback_POOL_LIST[ X_Callback_POOL_LIST.length ] = f;
\r
313 obj = f( X_Closure_COMMAND_BACK );
\r
315 if( obj.name ) delete obj.name;
\r
316 if( obj.func ) delete obj.func;
\r
317 if( obj.context ) delete obj.context;
\r
318 if( obj.supplement ) delete obj.supplement;
\r
325 function X_Callback_monitor(){
\r
327 'Callback:Live' : X_Callback_LIVE_LIST.length,
\r
328 'Callback:Pool' : X_Callback_POOL_LIST.length
\r
331 function X_Callback_gc(){
\r
332 X_Callback_POOL_LIST.length = 0; // ?
\r
335 X_TEMP.onSystemReady.push( function( sys ){
\r
336 sys.monitor( X_Callback_monitor );
\r
337 sys.gc( X_Callback_gc );
\r
341 console.log( 'X.Core.Callback' );
\r