1. 程式人生 > >用AOP來讓你的JS程式碼變得更有可維護性吧

用AOP來讓你的JS程式碼變得更有可維護性吧

此文已由作者吳佳祥授權網易雲社群釋出。

歡迎訪問網易雲社群,瞭解更多網易技術產品運營經驗。


好吧我承認這是篇任務。

最近看到個訊息,ES2017已經定稿了,心想,我去,還完全沒了解ES2016呢,ES8就定稿了,out了,這可咋辦,趕緊Google(Baidu)去!

不過從ES6(2015)之後,tc39的規劃是一年一個版本,所以ES7跟ES8也不會像ES6那麼大的步子。粗略瞟了一眼,咦,裝飾器(Decorator)還沒到 Stage 3啊,好吧不過已經到了2了,想必之後還是會慢慢納入的,就先了解一下吧。與之相關的,就是AOP(面向切面程式設計)和裝飾器模式了。

AOP是對OOP(面向物件程式設計)的一個橫向的補充,主要作用就是把一些業務無關的功能抽離出來,例如日誌列印、統計資料、資料驗證、安全控制、異常處理等等。這些功能都與某些核心業務無關,但又隨處可見,如果都是複製貼上未免太沒逼格,而且難以維護,不優雅。把它們抽離出來,用“動態”插入的方式嵌到各業務邏輯中。這樣的好處是業務模組可以變得比較乾淨,不受汙染,同時這些功能點能夠得到很好的複用,給模組解耦。


由於語言的特性,在JavaScript中可以輕鬆地實現AOP技術。


讓我們假設一個業務無關的功能--繫結變數:

   有三個變數a、b、c,要保證b、c在修改前後,a一直等於b與c的和。

先分析一下,此處的業務無關功能點(即切面)應該就是“a一直等於b與c的和”。即在業務邏輯走完之後,需要重新將b+c賦值給c;同時,我們還應該保證賦給b與c的值應該都是Number型別。


首先,我們先來個ES3版本都可以相容的辦法,使用裝飾器模式來實現AOP。

什麼叫裝飾器模式?即提供一個和原功能一樣呼叫方法的裝飾器,裝飾器裡邊植入了切面。由於在JS中函式是一等公民,所以我們可以提供一個裝飾器函式來實現AOP。

var a = b = c = 0;

function setA (action) {
    return function (value) {
        action(value);
        a = b + c;
    };
}

function validateValue (action) {
    return function (value) {
        if (typeof value !== 'number') {
            throw new Error('你傳了個什麼鬼進來');
        }
        action(value);
    };
}

var setB = validateValue(setA(function (value) {
    b = value;
}));

var setC = validateValue(setA(function (value) {
    c = value;
}));

setB(10);                    // a === 10;
setC(1);                     // a === 11;
setC('什麼鬼');              // Uncaught Error: 你傳了個什麼鬼進來

用裝飾器函式來實現AOP在實際的程式設計中還是挺有用的,有利於把邏輯劃分成更小粒度的模組,同時也符合函數語言程式設計(FP)的思想。


可是這個方法其實有個噁心的地方,我不想把賦值的“=”用函式來代替啊,腫麼破?

難(tao)過(yan)的是,JS中沒有提供運算子過載的功能。不過ES5中提供了一個新的API可以讓我們實現過載“=”運算子-- Object.defineProperty 以及 Object.defineProperties ,相關用法可以點選連結檢視。由於這個API的操作物件是一個Object,所以我們可以把a、b、c三個變數包在一個物件中。


var accessorDecorator = (function () {
    var context = {b:0,c:0};
    return {
        set: function (action) {
            return function (value) {
                action.call(this, value);
                wrapper.a = this.b + this.c;
            }.bind(this);
        }.bind(context),
        get: function (action) {
            return action.bind(context);
        }
    };
})();

var wrapper = Object.defineProperties({}, {
    a: {
        value: 0,
        writable: true
    },
    b: {
        set: validateValue(accessorDecorator.set(function (value) {
            this.b = value;
        })),
        get: accessorDecorator.get(function () {
            return this.b;
        })
    },
    c: {
        set: validateValue(accessorDecorator.set(function (value) {
            this.c = value;
        })),
        get: accessorDecorator.get(function () {
            return this.c;
        })
    }
});

function validateValue (action) {
    return function (value) {
        if (typeof value !== 'number') {
            throw new Error('你傳了個什麼鬼進來');
        }
        action(value);
    };
}

wrapper.b = 10;              // wrapper.a === 10;
wrapper.c = 1;               // wrapper.a === 11;
wrapper.c = '什麼鬼';        // Uncaught Error: 你傳了個什麼鬼進來

需要提及的一點是,現在非常流行火熱的Vue.js的資料繫結原理就是通過這個實現的。

在文章的開頭,還提到了新的ES草案--裝飾器語法(Decorator),事實上,這也算是 Object.defineProperty 的一個語法糖。使用裝飾器,可以讓我們上面的程式碼變得更簡潔(好吧這很JAVA)。

class Wrapper {
    a = 0;

    @validateValue
    @setA
    b = 0;

    @validateValue
    @setA
    c = 0;
}function validateValue (target, key, descriptor) {    const action = descriptor.set;
    descriptor.set = (value) => {        if (typeof value !== 'number') {            throw new Error('你傳了個什麼鬼進來');
        }
        action(value);
    };
}function setA (target, key, descriptor) {    const action = descriptor.set;
    descriptor.set = (value) => {
        action(value);
        target.a = target.b + target.c;
    };
}let wrapper = new Wrapper;


更JAVA的是,ES6中提供了 Proxy 與 Reflect 物件。所以我們的這段程式碼現在可以這麼寫:


let validateProxy = new Proxy({a: 0, b: 0, c: 0}, {
    set(target, key, value, receiver) {
        if (key in target && typeof value !== 'number') {
            throw new Error('你傳了個什麼鬼進來');
        }
        return Reflect.set(target, key, value, receiver);
    }
});

let wrapper = new Proxy(validateProxy, {
    set(target, key, value, receiver) {
        let done = Reflect.set(target, key, value, receiver);
        if (key === 'b' || key === 'c') {
            Reflect.set(target, 'a', target.b + target.c, receiver);
        }
        return done;
    }
});


隨著ECMAScript的新標準的定稿,AOP的實現在JavaScript中是越來越容易了。在實際編碼中使用AOP和裝飾器模式,可以將一些業務無關的程式碼從業務邏輯中抽離出來,使得業務邏輯更加清晰,不受汙染,同時也有利於這些業務無關程式碼的複用與維護。


網易雲免費體驗館,0成本體驗20+款雲產品! 

更多網易技術、產品、運營經驗分享請點選

相關文章:
【推薦】 一個體驗好的Windows 工作列縮圖開發心得