// 成员被定义在构造函数上
Object.defineProperty(Model, 'method3', {
value: function () {},
writable: true,
enumerable: false, // 设置不可被枚举
configurable: true
})
可以看出,只有method2是在实例化时才赋值的,一个不存在的属性是不会有descriptor的,所以这就是为什么TS在针对Property Decorator不传递第三个参数的原因,至于为什么静态成员也没有传递descriptor,目前没有找到合理的解释,但是如果明确的要使用,是可以手动获取的。
就像上述的示例,我们针对四个成员都添加了装饰器以后,method1和method2第一个参数就是Model.prototype,而method3和method4的第一个参数就是Model。
class Model {
// 实例成员
@instance
method1 () {}
@instance
method2 = () => {}
// 静态成员
@static
static method3 () {}
@static
static method4 = () => {}
}
function instance(target) {
console.log(target.constructor === Model)
}
function static(target) {
console.log(target === Model)
}
函数,访问器,和属性装饰器三者之间的区别
函数
首先是函数,函数装饰器的返回值会默认作为属性的value描述符存在,如果返回值为undefined则会忽略,使用之前的descriptor引用作为函数的描述符。
所以针对我们最开始的统计耗时的逻辑可以这么来做:
class Model {
@log1
getData1() {}
@log2
getData2() {}
}
// 方案一,返回新的value描述符
function log1(tag, name, descriptor) {
return {
...descriptor,
value(...args) {
let start = new Date().valueOf()
try {
return descriptor.value.apply(this, args)
} finally {
let end = new Date().valueOf()
console.log(`start: ${start} end: ${end} consume: ${end - start}`)
}
}
}
}
// 方案二、修改现有描述符
function log2(tag, name, descriptor) {
let func = descriptor.value // 先获取之前的函数
// 修改对应的value
descriptor.value = function (...args) {
let start = new Date().valueOf()
try {
return func.apply(this, args)
} finally {
let end = new Date().valueOf()
console.log(`start: ${start} end: ${end} consume: ${end - start}`)
}
}
}
访问器
访问器就是添加有get、set前缀的函数,用于控制属性的赋值及取值操作,在使用上与函数没有什么区别,甚至在返回值的处理上也没有什么区别。
只不过我们需要按照规定设置对应的get或者set描述符罢了:
class Modal {
_name = 'Niko'
@prefix
get name() { return this._name }
}
function prefix(target, name, descriptor) {
return {
...descriptor,
get () {
return `wrap_${this._name}`
}
}
}
console.log(new Modal().name) // wrap_Niko
属性
对于属性的装饰器,是没有返回descriptor的,并且装饰器函数的返回值也会被忽略掉,如果我们想要修改某一个静态属性,则需要自己获取descriptor:
class Modal {
@prefix
static name1 = 'Niko'
}
function prefix(target, name) {
let descriptor = Object.getOwnPropertyDescriptor(target, name)
Object.defineProperty(target, name, {
...descriptor,
value: `wrap_${descriptor.value}`
})
}
console.log(Modal.name1) // wrap_Niko
对于一个实例的属性,则没有直接修改的方案,不过我们可以结合着一些其他装饰器来曲线救国。
比如,我们有一个类,会传入姓名和年龄作为初始化的参数,然后我们要针对这两个参数设置对应的格式校验:
const validateConf = {} // 存储校验信息
@validator
class Person {
@validate('string')
name
@validate('number')
age
constructor(name, age) {
this.name = name
this.age = age
}
}
function validator(constructor) {
return class extends constructor {
constructor(...args) {
super(...args)