概念介绍
装饰器模式(Decorator Pattern)允许像一个现有的对象添加新的功能,同时又不改变其结构,这种类型的设计模式属于结构型模式,他是作为现有的类的一个包装。
这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名的完整性的前提下,提供了额外的功能。
我们通过下面的实例来演示装饰器模式的用法。其中,我们把一个形状装饰上不同的颜色,同时又不改变形状类
用js实现一个装饰器,看下原理
const dec = (target, property) => {
const old = target.prototype[property];
target.prototype[property] = (msg) => {
msg = `【${msg}】`;
old(msg)
}
}
class Log {
print(str: string) {
console.log(str);
}
}
dec(Log, "print");
const log = new Log()
log.print("你好")
在控制台里会打印出如下结果:
【你好】
关于类、方法、访问器、属性装饰器(Typescript中)
- 类装饰器:接收1个参数
(target)
、可以用来监视、修改和替换类定义 - 方法装饰器:接收3个参数
(target、property、descriptor)
可以用来监视、修改和替换方法定义 - 访问器装饰器:接收3个参数
(target、property、descriptor)
可以用来监视、修改和替换访问器定义 - 属性装饰器:接收2个参数
(target、property)
只能用来监视类中是否声明了某个名字的属性 - 参数装饰器:接收3个参数
(target、property、paramsIndex)
只能用来监视一个方法的参数是否被传入。
详解:
target:对于静态成员来说是类的构造函数,对于实例成员是类的原型对象(prototype)
property:成员的名字
descriptor:成员的属性描述符
paramsIndex:参数的下标
descriptor简单介绍
//descriptor 属性描述符
//在方法装饰器中
{
value: [Function],
writable: true,
enumerable: true,
configurable: true
}
//在访问器装饰器中
{
get: [Function: get],
set: undefined,
enumerable: false,
configurable: true
}
类装饰器
类装饰器应用于类构造函数,可以用来监视,修改或替换类定义。
注意 类的构造函数作为其唯一的参数,如果类装饰器返回一个值,他会使用提供的构造函数来替换类的声明
const anotation: Function = (target) => {
console.log(target.foo);
console.log(target.method);
return class extends target {
newMethod() {
console.log("Example New Method");
}
}
}
@anotation
class Example {
static foo = "fooValue";
public method() {
console.log("Example Public Method");
}
}
const example = new Example();
example["newMethod"]();
在控制台里会打印出如下结果:
fooValue
undefined
Example New Method
方法装饰器
用来监视,修改或者替换方法定义。
方法装饰器表达式会在运行时当作函数被调用,传入下列3个参数(target,property,descriptor):
- target:对于静态成员来说是类的构造函数(
包含全部的静态属性
),对于实例成员是类的原型对象(包含全部的实例属性
)。 - property:成员的名字。
- descriptor:成员的属性描述符。
第一种return target
装饰函数(想使用target装饰函数,必须return)
// 方法装饰器(return target)
const anotationMethod: Function = (target, property, descriptor) => {
const old = target[property];
target[property] = function () {
console.log("old calling");
old();
}
return target
}
class Example {
static foo = "fooValue";
@anotationMethod
public method() {
console.log("Example Public Method");
}
}
const example = new Example();
example.method();
在控制台里会打印出如下结果:
old calling
Example Public Method
2.第二种descriptor.value
装饰函数(直接修改descriptor.value即可)
// 方法装饰器(修改descriptor.vaue)
const anotationMethod: Function = (target, property, descriptor) => {
const old = descriptor.value;
descriptor.value = function () {
console.log("old calling");
old();
}
}
// 方法装饰器(修改descriptor.value)
class Example {
static foo = "fooValue";
@anotationMethod
public method() {
console.log("Example Public Method");
}
}
const example = new Example();
example.method();
在控制台会打印出如下结果
old calling
Example Public Method
访问器装饰器
访问器装饰器应用于访问器的 属性描述符并且可以用来监视,修改或替换一个访问器的定义。
注意 TypeScript不允许同时装饰一个成员的get和set访问器。取而代之的是,一个成员的所有装饰的必须应用在文档顺序的第一个访问器上。这是因为,在装饰器应用于一个属性描述符时,它联合了get和set访问器,而不是分开声明的。
访问器装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
- 成员的名字。
- 成员的属性描述符。
注意 如果代码输出目标版本小于ES5,Property Descriptor将会是undefined。
const configurable: Function = (target, property, descriptor: PropertyDescriptor) => {
console.log(target);
console.log(property);
console.log(descriptor);
descriptor.get = () => {
return "杨志强"
}
}
class Point {
private _name: string = "张三";
@configurable
get name() {
return this._name
}
set name(newVal) {
this._name = newVal;
}
}
const point = new Point();
console.log(point.name);
在控制台会输出如下结果
Point {}
name
{
get: [Function: get],
set: [Function: set],
enumerable: false,
configurable: true
}
杨志强
属性装饰器
属性装饰器只能用来监视类中是否声明了某个名字的属性。
因为目前没有办法在定义一个原型对象的成员时描述一个实例属性,并且没办法监视或修改一个属性的初始化方法。返回值也会被忽略。
属性装饰器表达式会在运行时当作函数被调用,传入下列俩个参数
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
- 成员的名字。
注意 属性描述符不会作为参数传入属性装饰器,这与Typescript是如何初始化属性装饰器有关
import "reflect-metadata"
const formatMetadataKey = Symbol("format");
const format = (value: string) => {
return Reflect.metadata(formatMetadataKey, value);
}
function getFormat(target, property) {
return Reflect.getMetadata(formatMetadataKey, target, property);
}
class Point {
@format("Hello World")
public name: string = "张三";
greet(property) {
return getFormat(this, property);
}
}
const point = new Point();
console.log(point.greet("name"));
这个@format("Hello World")
装饰器是个 装饰器工厂。 当 @format("Hello World")
被调用时,它会添加一条这个属性的元数据,通过reflect-metadata
库里的Reflect.metadata
函数。 当 getFormat
被调用时,它读取格式的元数据。
在控制台会打印如下结果
Hello World
参数装饰器
参数装饰器只能用来监视一个方法的参数是否被传入。
参数装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
- 成员的名字。
- 参数在函数参数列表中的索引。
import "reflect-metadata";
const requiredKey = Symbol("requeiredKey")
const required: Function = (target, property, paramIndex) => {
let paramIndexs = Reflect.getMetadata(requiredKey, target, property) || [];
paramIndexs.push(paramIndex);
Reflect.defineMetadata(requiredKey, paramIndexs, target, property);
}
// 此处为什么能用getOwnMetadata也能接收到?
// 答:因为装饰器接收的target对于实例成员来说类的原型对象(prototype),对于静态成员来说是类的构造函数,所以我们required和validate指向同一个对象(target.prototype),所以能获取元数据
const validate: Function = (target, property, descriptor: PropertyDescriptor) => {
const method = descriptor.value;
descriptor.value = function () {
const requeiredParamers = Reflect.getOwnMetadata(requiredKey, target, property);
if (requeiredParamers) {
for (let paramIndex of requeiredParamers) {
if (paramIndex >= arguments.length || arguments[paramIndex] === undefined) {
throw new Error("参数不正确");
}
}
}
method.apply(this, arguments);
}
}
class User {
name: string = "张三"
@validate
info(@required id?: number) {
console.log(`用户id为${id}`);
}
}
const user = new User();
user.info(1);
在控制台里面打印会打印如下结果
用户id为1
关于元数据方法的补充
Reflect.getMetadata(metadataKey,target,property):获取目标对象或其原型链上提供的元数据键的元数据值。
Reflect.getOwnMetadata(metadataKey,target,property)::获取目标对象上提供的元数据键的元数据值(目标对象指的不是this,而是未实例化的这个类)。
Reflect.defineMetadata(metadataKey,value,target,property):在目标上定义唯一的元数据项。
Reflect.metadata(metadataKey,value):可用于类、类成员或参数的默认元数据装饰器工厂。(一般直接return)
关于装饰器的执行顺序
如果我们使用装饰器工厂的话,可以通过下面的例子来观察它们求值的顺序:
function f() {
console.log("f(): evaluated");
return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("f(): called");
}
}
function g() {
console.log("g(): evaluated");
return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("g(): called");
}
}
class C {
@f()
@g()
method() {}
}
在控制台里面打印会打印如下结果
f(): evaluated
g(): evaluated
g(): called
f(): called