javascript 中 AOP 那些事

引子

最近在忙着閱讀 megalo 的代碼 (將來會出一個系列專門講 megalo 的源碼,仍是挺有意思的,你們能夠期待一下)。感受 megalompvue 等小程序的跨端框架也好, weex 跨平臺框架也好,本質都差很少,都是 fork 了一份 vue 過來改了改,藉助了 vue 的能力,在平臺具體的api上換成了本身的。javascript

其中,有一段代碼以爲挺有意思:vue

Vue.prototype._l = aop(Vue.prototype._l, {
    after: afterRenderList
  });
複製代碼

上面代碼,經過 aop 拓展了 Vue 原型上的 _l 方法,當 _l 方法執行完後,再執行 afterRenderListjava

那麼,什麼是 AOP ?編程

什麼是AOP

AOP(Aspect-Oriented Programming):面向切面的編程,是對面向對象編程(OOP)的補充。面向對象是縱向編程,繼承、封裝和多態,而面向切面編程補充面向對象的不足。小程序

OOP中,咱們關注的是(class),而在AOP中,咱們關注的是切面api

好比說,一次表單提交,有正常的業務提交過程,但咱們想在這個提交過程的橫向加一個表單驗證。或者一個正常的業務中,咱們但願橫向添加一些埋點功能,同時再橫向添加運行時錯誤信息收集的功能,同時還可以驗證一下是否有操做權限等,這些都是面向切面編程。服務器

通常來講,若是你遇到了須要從外部增長一些行爲,進而合併或修改既有行爲,或者把業務邏輯代碼和處理瑣碎事務的代碼分離開,以便可以分離複雜度等的業務場景,請必定要用好這種編程設計思想。weex

AOP比較典型的應用有:日誌記錄、性能監控、埋點上報、異常處理等等。app

那麼, javascript 中的 AOP 怎麼實現呢?框架

ES3 下經過高階函數實現

什麼是高階函數

高階函數接受一個或多個函數,並返回一個函數。

咱們常常用到的高階函數有:once, debounce, memoize, fluent

// 原函數
var takePhoto =function(){
 console.log('拍照片');
}
// 定義 aop 函數
var after=function( fn, afterfn ){
 return function(){
 let res = fn.apply( this, arguments );
 afterfn.apply( this, arguments );
 return res;
 }
}
// 裝飾函數
var addFilter=function(){
 console.log('加濾鏡');
}
// 用裝飾函數裝飾原函數
takePhoto=after(takePhoto,addFilter);
takePhoto();

複製代碼

再來一個 fluent 的例子:

function fluent(fn) {
 return function(...args) {
 fn.apply(this, args)
 return this
 }
}
function Person() {}
Person.prototype.setName = fluent(function(first, last) {
 this.first = first
 this.last = last
})
Person.prototype.sayName = fluent(function() {
 console.log(this.first, this.last)
})
var person = new Person()
person
 .setName('Jone', 'Doe')
 .sayName()
 .setName('John', 'Doe')
 .sayName()
複製代碼

這就是咱們標準非侵入地動態擴展屬性的方法:在執行原有代碼的基礎上再擴展所須要的功能。 事實上,megalo 的 aop 也是經過高階函數 本身實現的。

function aop(fn, options) {
            if (options === void 0) options = {};
            
            var before = options.before;
            var after = options.after;
            return function () {
              var args = [],
                  len = arguments.length;
              while (len--) {
                args[len] = arguments[len];
              }var self = this;
              
              if (before) {
                before.call.apply(before, [self, args].concat(args));
              }
              
              var ret = fn.call.apply(fn, [self].concat(args));
              
              if (after) {
                after.call.apply(after, [self, ret].concat(args, [ret]));
              }
              
              return ret;
            };
          }
複製代碼

上面的 aop 函數,會給源函數 fn 拓展 beforeafter 方法。

ES5 下裝飾者的實現

在 ES5 中引入了Object.defineProperty,咱們能夠更方便的給對象添加屬性:

let takePhoto = function () {
 console.log('拍照片');
}
// 給 takePhoto 添加屬性 after
Object.defineProperty(takePhoto, 'after', {
 writable: true,
 value: function () {
 console.log('加濾鏡');
 },
});
// 給 takePhoto 添加屬性 before
Object.defineProperty(takePhoto, 'before', {
 writable: true,
 value: function () {
 console.log('打開相機');
 },
});
// 包裝方法
let aop = function (fn) {
 return function () {
 fn.before()
 fn()
 fn.after()
 }
}
takePhoto = aop(takePhoto)
takePhoto()
複製代碼

基於原型鏈和類的裝飾者實現

class Test {
 takePhoto() {
 console.log('拍照');
 }
}
// after AOP
function after(target, action, fn) {
 let old = target.prototype[action];
 if (old) {
 target.prototype[action] = function () {
 let self = this;
 fn.bind(self);
 fn(handle);
 }
 }
}
// 用 AOP 函數修飾原函數
after(Test, 'takePhoto', () => {
 console.log('添加濾鏡');
});
let t = new Test();
t.takePhoto();

複製代碼

使用 ES7 修飾器實現裝飾者

Decorator 提案通過了大幅修改,目前尚未定案,不知道語法會不會再變。

場景:性能上報

典型的場景是記錄某異步請求請求耗時的性能數據並上報:

場景:異常處理

咱們能夠對原有代碼進行簡單的異常處理,而無需侵入式的修改

好比說,window.onerror 不能夠捕獲 異步的錯誤,好比說setTimeout, 因此會有人這樣操做:

var _setTimeout = window.setTimeout
window.setTimeout = function(cb, timeout) {
 var args = Array.prototype.slice.call(arguments, 2)
 return _setTimeout(function() {
 try {
 cb(...args)
 } catch (error) {
 // 對 error 進行加工後上報給服務器
 reportError(e)
 throw error
 }
 }, timeout)
}
複製代碼
相關文章
相關標籤/搜索