裝飾器(decorator)模式可以在不改變對象自身的基礎上,在程序運行期間給對像動態的添加方法或屬性。與繼承相比,裝飾者是一種更輕便靈活的作法。緩存
簡單說:容許向一個現有的對象添加新的功能,同時又不改變其原有結構。bash
咱們在修改老項目,或者與同事合做開發一個任務的時候,會發如今一份你沒法更改的js文件裏定義了window.onload方法,而你也須要在window.onload方法裏作些操做,那麼這種狀況下改怎麼辦呢?app
// 同事的代碼
window.onload = () => {
console.log('同事的處理邏輯')
}
// 裝飾者
let _onload= window.onload || function () {} // 將原函數緩存下來
window.onload = () => {
_onload()
console.log('本身的處理函數')
};
複製代碼
可是上述寫法也有幾個弊端:函數
1)須要多維護了一箇中間變量_onload,當裝飾鏈比較長或者須要裝飾的函數變多的時候,中間變量會愈來愈多。優化
2)會有this被劫持的風險。ui
針對上述兩個弊端,接下來就介紹一個AOP函數來優化這種裝飾:this
AOP裝飾函數是把一些跟核心業務邏輯無關的功能抽離出來,好比說日誌統計等;也能夠執行一些業務邏輯的前置操做,好比說參數校驗等。spa
例如咱們平時寫業務邏輯的時候,一般會在最後加上埋點邏輯,以下:prototype
function event () {
console.log('業務邏輯');
console.log('埋點邏輯');
}
event();
複製代碼
這種寫法在功能上沒有什麼問題,可是其實咱們能夠把業務邏輯和埋點邏輯分離開,讓代碼邏輯顯得更清晰。代理
Function.prototype.after = function(afterfn) {
let _this = this; // 將原函數的引用緩存下來
return function () { // 返回一個添加了本身邏輯(afterfn)的新函數
let ret = _this.apply(this, arguments) // 執行原函數,而且保存住返回結果
afterfn.apply(this, arguments) // 執行本身的函數,而且保證this不會被劫持,同時本身的函數接受的參數和原函數同樣
return ret
}
}
function event () {
console.log('業務邏輯');
}
// 利用after裝飾器給event加上埋點邏輯
event = event.after(() => {
console.log('埋點邏輯');
})
event();
複製代碼
利用上面的AOP裝飾函數,咱們回過頭來看下最初對window.onload的裝飾,針對前面提出的兩點弊端,還能夠再優化一下:
Function.prototype.after = function(afterfn) {
let _this = this
return function () {
let ret = _this.apply(this, arguments)
afterfn.apply(this, arguments)
return ret
}
}
window.onload = function(){
console.log('同事的處理邏輯')
}
window.onload = ( window.onload || function(){} ).after(function(){
console.log('本身的處理函數')
}.after(function(){
console.log('本身的第二個處理函數')
});
複製代碼
並且這種寫法是能夠在後面不停加 .after(function () {}) ,能夠連續的加上各個邏輯塊。
說到裝飾器模式,其實很容易聯想到ES7的裝飾器,那麼咱們來簡單看看ES7的裝飾器。
咱們本身先建一個class,做爲基礎類。
class Base {
constructor(val = 1){
this.init(val);
}
init (val) {
this.val = val;
}
getVal() {
console.log(this.val)
}
}
var base = new Base();
console.log(base.getVal()); // 1
複製代碼
如今咱們要作在Base初始化數據時默認加上100做爲基數,能夠給init方法加個裝飾器,寫法以下:
function decorateInit(target, key, descriptor) {
const method = descriptor.value; //method就是init的原函數
let baseNum = 100;
descriptor.value = (...args)=>{ // 重寫原函數
args[0] += baseNum;
let ret = method.apply(target, args); // 將修改後的參數傳遞給原函數
return ret;
}
return descriptor;
}
class Base {
constructor(val = 1){
this.init(val);
}
@decorateInit
init (val) {
this.val = val;
}
getVal() {
console.log(this.val)
}
}
var base = new Base();
console.log(base.getVal()); // 101
複製代碼
首先看到在init方法上多了個 @decorateInit ,這個其實就是裝飾器。
而在 decorateInit 方法中接受3個參數,這裏插一個小知識點。在ES6的class語法糖中「」
class A {
sayHello () {
console.log('hello world');
}
}
複製代碼
在給類添加方法時,實際上是調用 Object.defineProperty 這個方法,而 Object.defineProperty 方法咱們都知道接受三個參數,target 、name 和 descriptor。因此建立一個Class而且添加一個方法等價於下面這段代碼。
function A () {}
Object.defineProperty(A.prototype, 'sayHello', {
value: function() { console.log('hello world'); },
enumerable: false,
configurable: true,
writable: true
})
複製代碼
而裝飾器也是利用了 Object.defineProperty,因此傳參也就一致了。第一個參數爲類的原型對象,第二個參數爲要裝飾的屬性名,第三個參數是該屬性的描述對象。固然,裝飾器也是能夠傳參的,看下面一種寫法:
function decorateInit(num)
return function (target, key, descriptor) {
const method = descriptor.value; //method就是init的原函數
let baseNum = num || 100;
descriptor.value = (...args)=>{ // 重寫原函數
args[0] += baseNum;
let ret = method.apply(target, args); // 將修改後的參數傳遞給原函數
return ret;
}
return descriptor;
}
}
class Base {
constructor(val = 1){
this.init(val);
}
@decorateInit(20)
init (val) {
this.val = val;
}
getVal() {
console.log(this.val)
}
}
var base = new Base();
console.log(base.getVal()); // 21
複製代碼
其實就是在原有的裝飾器函數外再嵌套一層函數。
利用前面的知識,咱們先寫個AOP裝飾函數(after)綁定在Function原型上,而後在裝飾器(log)中重寫函數(裝飾器模式),以達到先執行業務邏輯,再執行裝飾器中的埋點邏輯的目的。最後在Vue的methods裏給具體的方法加上裝飾器。具體代碼以下:
// 第一部分就是AOP裝飾函數
Function.prototype.after = function (afterfn) {
let _self = this;
return function () {
let ret = _self.apply(this, arguments);
afterfn.apply(this, arguments);
return ret;
};
};
// 第二部分是一個能接收參數的log裝飾器函數
const log = function (stat) {
return function (target, key, descriptor) {
descriptor.value = descriptor.value.after(() => {
console.log('埋點邏輯' + stat)
});
};
};
// 以上兩個方法其實能夠封裝在基礎類中
// Vue項目組件中能夠添加裝飾器
export default {
name: 'List',
data () {
return {}
},
methods: {
@log('123456')
event () {
console.log('業務邏輯');
}
},
};
複製代碼
裝飾器模式和代理模式都是對現有對象功能的擴展,可是二者的出發點不一樣。裝飾者模式偏向於增長對象功能的同時作到解耦;代理模式偏向於對對象的輔助或者流程的把控。
裝飾器模式的優勢:
一、能夠動態擴展一個對象;
二、複用性較強。能夠給一個對象屢次增長同一個裝飾器,也能夠用同一個裝飾器來裝飾不一樣的對象。
缺點:
一、多層裝飾使用起來相對比較複雜。