什么是裝飾器
在面向?qū)ο螅∣OP)的設(shè)計模式中,decorator被稱為裝飾模式。我們在程序開發(fā)中,許多時候并不希望某個類天生就非常龐大,一次性包含許多職責。裝飾者模式可以動態(tài)地給某個對象添加一些額外的功能,而不會影響從這個類中派生的其他對象。
JS的裝飾器
為了更簡便的使用裝飾器模式,ES7引入了一個新的語法,依賴于 ES5 的Object.defineProperty方法。
修飾器的用法
類的裝飾
class A {}
// 等同于
class A {}
A = decorator(A) || A;
也就是說,裝飾器是一個對類進行處理的函數(shù)。裝飾器函數(shù)的第一個參數(shù),就是所要修飾的目標類。
如果覺得一個參數(shù)不夠用,可以在裝飾器外面再封裝一層函數(shù)。
function testable(isTestable) {
return function(target) {
target.isTestable = isTestable;
}
}
@testable(true)
class MyTestableClass {}
MyTestableClass.isTestable // true
@testable(false)
class MyClass {}
MyClass.isTestable // false
簡單來說JS 的裝飾器可以用來“裝飾”三種類型的對象:類的屬性/方法、訪問器、類本身
針對屬性/方法的裝飾器
class Person {
@readonly
name() { return `${this.first} ${this.last}` }
}
function readonly(target, name, descriptor){
// descriptor對象原來的值如下
// {
// value: specifiedFunction,
// enumerable: false,
// configurable: true,
// writable: true
// };
descriptor.writable = false;
return descriptor;
}
readonly(Person.prototype, 'name', descriptor);
// 類似于
Object.defineProperty(Person.prototype, 'name', descriptor);
- 裝飾器第一個參數(shù)是類的原型對象,上例是 Person.prototype,裝飾器的本意是要“裝飾”類的實例,但是這個時候?qū)嵗€沒生成,所以只能去裝飾原型(這不同于類的裝飾,那種情況時target參數(shù)指的是類本身);
- 第二個參數(shù)是 所要裝飾的屬性名
- 第三個參數(shù)是 該屬性的描述對象
函數(shù)方法的裝飾
由于存在函數(shù)提升,使得修飾器不能用于函數(shù)。類是不會提升的,所以就沒有這方面的問題。
可以采用高階函數(shù)的形式直接執(zhí)行。
function doSomething(name) {
console.log('Hello, ' + name);
}
function loggingDecorator(wrapped) {
return function() {
console.log('Starting');
const result = wrapped.apply(this, arguments);
console.log('Finished');
return result;
}
}
const wrapped = loggingDecorator(doSomething);
// decorator 外部可以包裝一個函數(shù),函數(shù)可以帶參數(shù)
function Decorator(type){
/**
* 這里是真正的 decorator
* @target 裝飾的屬性所述的類的原型,注意,不是實例后的類。如果裝飾的是 Car 的某個屬性,這個 target 的值就是 Car.prototype
* @name 裝飾的屬性的 key
* @descriptor 裝飾的對象的描述對象
*/
return function (target, name, descriptor){
// 以此可以獲取實例化的時候此屬性的默認值
let v = descriptor.initializer && descriptor.initializer.call(this);
// 返回一個新的描述對象,或者直接修改 descriptor 也可以
return {
enumerable: true,
configurable: true,
get: function() {
return v;
},
set: function(c) {
v = c;
}
}
}
}