装饰器模式(Decorator)的ES7实现
装饰器模式(Decorator)的ES7实现
装饰器模式是一种结构型设计模式,允许向现有对象动态添加新功能而不改变其结构。ES7引入的装饰器语法让这种模式在JavaScript中实现变得更加优雅。装饰器本质上是一个高阶函数,能够修改类、方法、属性或参数的行为。
装饰器基础语法
ES7装饰器使用@
符号表示,可以应用于类、类方法、访问器、属性和参数。装饰器本质上是一个函数,接收目标对象的相关信息作为参数。
// 类装饰器
@decorator
class MyClass {}
// 方法装饰器
class MyClass {
@decorator
method() {}
}
// 属性装饰器
class MyClass {
@decorator
property = 'value'
}
// 访问器装饰器
class MyClass {
@decorator
get value() {}
}
实现简单的装饰器
装饰器函数接收三个参数:目标对象、属性名和属性描述符。对于类装饰器,只接收构造函数作为参数。
function log(target, name, descriptor) {
const original = descriptor.value
descriptor.value = function(...args) {
console.log(`调用 ${name} 参数: ${args}`)
return original.apply(this, args)
}
return descriptor
}
class Calculator {
@log
add(a, b) {
return a + b
}
}
const calc = new Calculator()
calc.add(2, 3) // 输出: 调用 add 参数: 2,3
装饰器工厂
装饰器工厂是返回装饰器函数的函数,允许传递参数定制装饰器行为。
function logWithMessage(message) {
return function(target, name, descriptor) {
const original = descriptor.value
descriptor.value = function(...args) {
console.log(`${message}: 调用 ${name}`)
return original.apply(this, args)
}
return descriptor
}
}
class User {
@logWithMessage('用户操作')
login(username, password) {
// 登录逻辑
}
}
类装饰器实现
类装饰器应用于类构造函数,可以用来修改或替换类定义。
function sealed(constructor) {
Object.seal(constructor)
Object.seal(constructor.prototype)
}
@sealed
class Animal {
constructor(name) {
this.name = name
}
speak() {
console.log(`${this.name} makes a noise.`)
}
}
属性装饰器应用
属性装饰器可以用于观察属性变化或添加元数据。
function readonly(target, name, descriptor) {
descriptor.writable = false
return descriptor
}
class Circle {
@readonly
PI = 3.14159
constructor(radius) {
this.radius = radius
}
}
const circle = new Circle(5)
circle.PI = 3.14 // 报错: Cannot assign to read only property 'PI'
多个装饰器组合
装饰器可以叠加使用,执行顺序是从下到上(声明时从近到远)。
function first() {
console.log('first(): 装饰器工厂')
return function(target, propertyKey, descriptor) {
console.log('first(): 装饰器')
}
}
function second() {
console.log('second(): 装饰器工厂')
return function(target, propertyKey, descriptor) {
console.log('second(): 装饰器')
}
}
class Example {
@first()
@second()
method() {}
}
// 输出顺序:
// second(): 装饰器工厂
// first(): 装饰器工厂
// second(): 装饰器
// first(): 装饰器
实际应用场景
装饰器在Web开发中有多种实用场景,如日志记录、性能监测、表单验证等。
// 性能计时装饰器
function time(label = '') {
return function(target, name, descriptor) {
const original = descriptor.value
descriptor.value = async function(...args) {
const start = performance.now()
const result = await original.apply(this, args)
console.log(`${label || name} 耗时: ${performance.now() - start}ms`)
return result
}
return descriptor
}
}
class API {
@time('获取用户数据')
async fetchUser(id) {
// 模拟API请求
await new Promise(resolve => setTimeout(resolve, 500))
return { id, name: 'John Doe' }
}
}
元数据装饰器
装饰器可以用于添加元数据,配合Reflect Metadata实现更强大的功能。
import 'reflect-metadata'
function validate(type) {
return function(target, key, descriptor) {
Reflect.defineMetadata('validation:type', type, target, key)
const original = descriptor.value
descriptor.value = function(value) {
if (typeof value !== type) {
throw new Error(`参数 ${key} 必须是 ${type} 类型`)
}
return original.call(this, value)
}
return descriptor
}
}
class Validator {
@validate('string')
setName(name) {
this.name = name
}
}
const validator = new Validator()
validator.setName('Alice') // 正常
validator.setName(123) // 报错: 参数 name 必须是 string 类型
自动绑定this
装饰器可以解决类方法中this丢失的问题,自动绑定实例。
function autobind(target, name, descriptor) {
const originalMethod = descriptor.value
return {
configurable: true,
get() {
const boundFn = originalMethod.bind(this)
Object.defineProperty(this, name, {
value: boundFn,
configurable: true,
writable: true
})
return boundFn
}
}
}
class Button {
@autobind
handleClick() {
console.log(this) // 始终指向Button实例
}
}
const button = new Button()
document.addEventListener('click', button.handleClick)
装饰器与高阶组件
在React中,装饰器常用于创建高阶组件(HOC)。
function withLoading(WrappedComponent) {
return class extends React.Component {
state = { loading: true }
async componentDidMount() {
// 模拟数据加载
await new Promise(resolve => setTimeout(resolve, 1000))
this.setState({ loading: false })
}
render() {
return this.state.loading
? <div>Loading...</div>
: <WrappedComponent {...this.props} />
}
}
}
@withLoading
class UserProfile extends React.Component {
render() {
return <div>User Profile Content</div>
}
}
装饰器的局限性
虽然装饰器功能强大,但需要注意几个限制:不能装饰函数(非类方法)、不能修改类继承关系、在静态分析工具中可能不被完全支持。此外,装饰器目前仍是ECMAScript的提案,需要使用Babel等工具转换才能运行。
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn