最近在忙着閱讀 megalo
的代碼 (將來會出一個系列專門講 megalo
的源碼,仍是挺有意思的,你們能夠期待一下)。感受 megalo
、 mpvue
等小程序的跨端框架也好, weex
跨平臺框架也好,本質都差很少,都是 fork
了一份 vue
過來改了改,藉助了 vue
的能力,在平臺具體的api
上換成了本身的。javascript
其中,有一段代碼以爲挺有意思:vue
Vue.prototype._l = aop(Vue.prototype._l, {
after: afterRenderList
});
複製代碼
上面代碼,經過 aop
拓展了 Vue 原型上的 _l
方法,當 _l
方法執行完後,再執行 afterRenderList
。java
那麼,什麼是 AOP
?編程
AOP
(Aspect-Oriented Programming):面向切面的編程,是對面向對象編程(OOP)的補充。面向對象是縱向編程,繼承、封裝和多態,而面向切面編程補充面向對象的不足。小程序
在OOP
中,咱們關注的是類(class),而在AOP中,咱們關注的是切面。api
好比說,一次表單提交,有正常的業務提交過程,但咱們想在這個提交過程的橫向加一個表單驗證。或者一個正常的業務中,咱們但願橫向添加一些埋點功能,同時再橫向添加運行時錯誤信息收集的功能,同時還可以驗證一下是否有操做權限等,這些都是面向切面編程。服務器
通常來講,若是你遇到了須要從外部增長一些行爲,進而合併或修改既有行爲,或者把業務邏輯代碼和處理瑣碎事務的代碼分離開,以便可以分離複雜度等的業務場景,請必定要用好這種編程設計思想。weex
AOP比較典型的應用有:日誌記錄、性能監控、埋點上報、異常處理等等。app
那麼, javascript 中的 AOP 怎麼實現呢?框架
什麼是高階函數?
高階函數接受一個或多個函數,並返回一個函數。
咱們常常用到的高階函數有: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
拓展 before
和 after
方法。
在 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();
複製代碼
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)
}
複製代碼