ZeptoのCallbacksモジュール実装の核心技術

ZeptoのCallbacksモジュールは、Deferredオブジェクトの基盤となるコールバック管理機構を提供します。このモジュールは非同期処理の柔軟な制御を可能にし、AjaxモジュールのPromiseスタイル実装を支えています。以下、その内部仕組みを詳細に解説します。

CallbacksモジュールはZeptoオブジェクトに追加されるファクトリ関数で、オプション設定によりコールバック管理オブジェクトを生成します。

(function($) {
  $.Callbacks = function(config) {
    // 実装コード
  };
})(Zepto);

configパラメータには以下の動作制御フラグを指定可能:

  • once: コールバックを1回のみ実行
  • memory: 最終実行時の引数を保持し新規追加時に即時実行
  • stopOnFalse: false返却時に処理中断
  • unique: 同一関数の重複追加禁止

実際の使用例を確認します。

const handler = $.Callbacks({ memory: true });
const processX = (val) => console.log('X:', val);
const processY = (val) => console.log('Y:', val);
const processZ = (val) => console.log('Z:', val);

handler.add(processX).add(processY).add(processZ);
handler.remove(processZ);
handler.fire('initial'); // X: initial, Y: initial

handler.lock();
handler.fire('locked'); // 出力なし

handler.add((val) => console.log('after lock:', val));
// after lock: initial

handler.disable();
handler.add((val) => console.log('disabled:', val));
// 何も出力されない

memoryオプション有効時、lock後に追加したコールバックも直ちに実行されます。disable後は追加処理が無効化されます。

fireメソッドはコールバック実行の中心処理です。

fire = function(args) {
  const [ctx, params] = args;
  if (config.memory) lastArgs = { ctx, params };
  isTriggered = true;
  let currentPos = startIdx || 0;
  startIdx = 0;
  const listSize = callbacks.length;
  isProcessing = true;

  while (callbacks && currentPos < listSize) {
    const result = callbacks[currentPos].apply(ctx, params);
    if (result === false && config.stopOnFalse) {
      lastArgs = null;
      break;
    }
    currentPos++;
  }
  isProcessing = false;

  if (callbacks) {
    if (pendingList) {
      if (pendingList.length) fire(pendingList.shift());
    } else if (lastArgs) {
      callbacks.length = 0;
    } else {
      disable();
    }
  }
};

引数をコンテキストとパラメータに分解し、memoryモードでは保存します。コールバックを順次実行し、stopOnFalse有効時はfalse返却で中断。実行後、待機キューがあれば処理し、memoryモード時はリストをクリアします。

addメソッドはコールバック追加処理です。

add: function() {
  if (callbacks) {
    const origLength = callbacks.length;
    const addCallback = (items) => {
      items.forEach(item => {
        if (typeof item === 'function') {
          if (!config.unique || !has(item)) callbacks.push(item);
        } else if (Array.isArray(item)) {
          addCallback(item);
        }
      });
    };
    addCallback(arguments);
    if (isProcessing) {
      listSize = callbacks.length;
    } else if (lastArgs) {
      startIdx = origLength;
      fire(lastArgs);
    }
  }
  return this;
},

追加時のリスト長を記録し、実行中はリストサイズを更新。memoryモード時は新規追加分を即時実行します。

removeメソッドは指定コールバックの削除処理です。

remove: function() {
  if (callbacks) {
    Array.from(arguments).forEach(target => {
      let pos = -1;
      while ((pos = callbacks.indexOf(target, pos + 1)) !== -1) {
        callbacks.splice(pos, 1);
        if (isProcessing) {
          if (pos < listSize) listSize--;
          if (pos < currentPos) currentPos--;
        }
      }
    });
  }
  return this;
},

実行中はインデックス調整を行い、処理の整合性を維持します。

fireWithメソッドはコンテキスト指定での実行処理です。

fireWith: function(ctx, params) {
  if (callbacks && (!isTriggered || pendingList)) {
    params = params || [];
    const args = [ctx, Array.isArray(params) ? params : [params]];
    if (isProcessing) {
      pendingList.push(args);
    } else {
      fire(args);
    }
  }
  return this;
},

lockメソッドとdisableメソッドの差異はmemoryモード時の挙動にあります。lockはpendingListを無効化し、memory存在時はdisableされませんが、disableは全状態をクリアします。

タグ: zepto javascript callbacks deferred event-handling

6月4日 17:10 投稿