js 支持 Aspect 切面編程

系列文章:讀 arale 源碼之 class 篇app

使用 Aspect,能夠容許你在指定方法執行的先後插入特定函數函數


before object.before(methodName, callback, [context])

在 object[methodName] 方法執行前,先執行 callback 函數.this

callback 函數在執行時,接收的參數和傳給 object[methodName] 相同。prototype

dialog.before('show', function(a, b) {
    a; // 1
    b; // 2
});

dialog.show(1, 2)

after object.after(methodName, callback, [context])

在 object[methodName] 方法執行後,再執行 callback 函數.code

callback 函數在執行時,接收的參數第一個是 object[methodName] 的返回值,以後的參數和傳給 object[methodName] 相同。對象

dialog.after('show', function(returned, a, b) {
  returned; // show 的返回值
    a; // 1
    b; // 2
});

dialog.show(1, 2);

源碼

定義兩個出口事件

exports.before = function(methodName, callback, context) {
    return weave.call(this, "before", methodName, callback, context);
};

exports.after = function(methodName, callback, context) {
    return weave.call(this, "after", methodName, callback, context);
};

定義一個可柯里化的函數 weave,柯里化成 after、before 函數get

function weave(when, methodName, callback, context) {
    var names = methodName.split(/\s+/);
    var name, method;
    while (name = names.shift()) {
      // this 指向基於 Base 生成的類的實例,如上例的 dialog
    method = getMethod(this, name);
    // 若是該函數(show)是第一次切面化綁定,則包裝該函數。
    // 被包裝的函數在執行先後都會觸發下面註冊的事件。
    if (!method.__isAspected) {
      wrap.call(this, name);
    }
    // 註冊事件:例如 after:show 、 before:show .
    this.on(when + ":" + name, callback, context);
  }
  return this;
}

若是沒有在實例中找到該方法(show),則拋出異常。源碼

// 在實例中查找該方法,若沒有則拋出異常
function getMethod(host, methodName) {
    var method = host[methodName];
    if (!method) {
    throw new Error('Invalid method name: ' + methodName);
  }
  return method;
}

定義一個包裝器函數,被包裝的函數在執行先後(before、after)都會觸發一個特定的函數it

// 包裝器,使該函數(show)支持切面化
function wrap(methodName) {
  // 保存舊的方法,this 指向該對象(dialog)
    var old = this[methodName];
    // 定義新的方法,並在舊方法以前觸發 before 綁定的函數,以後觸發 after 綁定的函數
    this[methodName] = function() {
    var args = Array.prototype.slice.call(arguments);
    var beforeArgs = ["before:" + methodName].concat(args);
    // 觸發 before 綁定的函數,若是返回 false 則阻止原函數 (show) 執行
    if (this.trigger.apply(this, beforeArgs) === false) return;
    // 執行舊的函數,並將返回值看成參數傳遞給 after 函數
    var ret = old.apply(this, arguments);
    var afterArgs = ["after:" + methodName, ret].concat(args);
    // 觸發 after 綁定的函數,綁定的函數的第一個參數是舊函數的返回值
    this.trigger.apply(this, afterArgs);
    return ret;
  }
  // 包裝以後打個標記,不用再重複包裝了
  this[methodName].__isAspected = true;
}
相關文章
相關標籤/搜索