Last updated on December 6, 2025 am
这是JavaScript进阶系列文章的第11篇文章, 今天我们来讲讲装饰器(Decorator),内容有点长,代码有些多,坐稳发车了。
在前端开发中,我们常面临这样的困境:如何在不修改原有代码的前提下,为类、方法或属性添加日志、权限校验、缓存等通用功能?
如果直接在业务逻辑中嵌入这些辅助代码,会导致核心逻辑与通用逻辑耦合,代码可读性和可维护性大幅下降。而 ES6 提案中Decorator(装饰器),正是为解决这一问题而生的优雅方案。
本文将从 Decorator 的核心本质出发,详解其语法特性、使用场景,并结合实战案例说明其在项目中的价值,帮助开发者真正掌握这一“非侵入式扩展”的利器。
Decorator 是什么?
核心定义
Decorator 是基于 “包装模式”(Wrapper Pattern) 的语法糖,其核心目标是:在不侵入原有代码结构的前提下,为类、类成员(方法/属性)动态扩展功能。
它的本质是一个特殊函数:接收被装饰的“目标对象”作为参数,通过修改或包装目标对象,返回增强后的新对象。简单来说,Decorator 就像给礼物包装——礼物本身(核心业务逻辑)不变,但包装(通用辅助逻辑)能让它具备额外的“属性”(如美观、防护)。
核心价值
Decorator 最核心的价值是 “分离关注点”:将日志、权限、缓存等通用逻辑与核心业务逻辑解耦,实现通用逻辑的复用,让代码更简洁、可维护。
举个直观的对比:
- 无 Decorator:在每个需要日志的方法中重复写
console.log,业务代码与日志逻辑混杂;
- 有 Decorator:写一个通用日志装饰器,通过
@log 语法一键应用到任意方法,业务代码保持纯净。
Decorator 的核心用法
Decorator 的语法简洁,核心分为“类装饰器”和“类成员装饰器”两类,各自承担不同的扩展职责。
1. 类装饰器:增强整个类
类装饰器直接作用于类本身,用于为类添加静态属性、静态方法,或修改类的原型(prototype)。
语法与参数
- 仅接收 1 个参数
target:被装饰的类本身;
- 可选返回值:若返回新类,则新类会替代原类;若不返回,则默认使用修改后的原类。
实战示例:给类添加通用能力
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| function withConfig(defaultConfig) { return function (target) { target.defaultConfig = defaultConfig; target.mergeConfig = function (userConfig) { return { ...defaultConfig, ...userConfig }; }; const originalConstructor = target; const newConstructor = function (...args) { originalConstructor.apply(this, args); this.config = target.mergeConfig(args[0] || {}); }; newConstructor.prototype = Object.create(originalConstructor.prototype); newConstructor.prototype.constructor = newConstructor; return newConstructor; }; }
@withConfig({ theme: "light", size: "medium" }) class Button { constructor(userConfig) { this.name = "按钮"; } getStyle() { return `主题:${this.config.theme},尺寸:${this.config.size}`; } }
console.log(Button.defaultConfig); const darkButton = new Button({ theme: "dark" }); console.log(darkButton.config); console.log(darkButton.getStyle());
|
2. 类成员装饰器:增强方法/属性
类成员装饰器作用于类的方法、属性或访问器(getter/setter),是开发中最常用的场景。
语法与参数
接收 3 个核心参数:
target:类的原型(针对实例方法)或类本身(针对静态方法);
name:被装饰的成员名称(方法名/属性名);
descriptor:成员的属性描述符(Object.getOwnPropertyDescriptor(target, name) 的返回值),包含 value(方法本身)、writable(是否可修改)、enumerable(是否可枚举)等属性。
实战示例:包装方法实现日志记录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| function logPerformance(target, name, descriptor) { const originalMethod = descriptor.value; descriptor.value = async function (...args) { const start = Date.now(); console.log(`[${name}] 开始执行,参数:`, args); try { const result = await originalMethod.apply(this, args); console.log(`[${name}] 执行成功,返回值:`, result); return result; } catch (error) { console.error(`[${name}] 执行失败,错误:`, error); throw error; } finally { const duration = Date.now() - start; console.log(`[${name}] 执行耗时:${duration}ms`); } }; return descriptor; }
class DataService { @logPerformance async fetchData(url) { const response = await fetch(url); return response.json(); } }
const service = new DataService(); service.fetchData("https://api.example.com/data");
|
三、Decorator 的典型使用场景:让通用逻辑复用更优雅
Decorator 的核心优势在于通用逻辑的复用,以下是前端开发中最实用的 6 个场景,覆盖日志、权限、缓存等高频需求。
1. 日志与性能监控
为核心业务方法(如接口请求、数据处理)添加日志,自动记录调用参数、返回值、执行时间,无需侵入业务代码。适合用于线上问题排查和性能优化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| function log(options = { enable: true, format: "default" }) { return function (target, name, descriptor) { if (!options.enable) return descriptor; const original = descriptor.value; descriptor.value = function (...args) { const time = new Date().toLocaleString(); if (options.format === "detail") { console.log(`[${time}] [${target.constructor.name}.${name}] 调用参数:`, args); } else { console.log(`[${time}] [${name}] 调用参数:`, args); } return original.apply(this, args); }; return descriptor; }; }
class OrderService { @log({ enable: true, format: "detail" }) createOrder(params) { console.log("创建订单:", params); return { orderId: "123456" }; } }
|
2. 权限校验与访问控制
限制敏感方法的调用权限(如管理员操作、用户登录态校验),将权限逻辑与业务逻辑分离,避免在每个方法中重复写校验代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| function requireRoles(...allowedRoles) { return function (target, name, descriptor) { const original = descriptor.value; descriptor.value = function (...args) { const userRole = this.getCurrentUserRole(); if (!allowedRoles.includes(userRole)) { throw new Error(`权限不足!当前角色:${userRole},允许角色:${allowedRoles.join(",")}`); } return original.apply(this, args); }; return descriptor; }; }
class SystemManager { getCurrentUserRole() { return "admin"; } @requireRoles("admin", "super_admin") deleteUser(userId) { console.log(`删除用户:${userId}`); } @requireRoles("user", "admin") queryUser(userId) { console.log(`查询用户:${userId}`); } }
|
3. 缓存优化(记忆化)
对计算密集型方法(如递归、大数据处理)或高频调用的接口添加缓存,避免重复计算或请求,提升性能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| function cache(options = { ttl: Infinity }) { const cacheMap = new Map(); return function (target, name, descriptor) { const original = descriptor.value; descriptor.value = function (...args) { const cacheKey = `${name}_${JSON.stringify(args)}`; if (cacheMap.has(cacheKey)) { const { data, timestamp } = cacheMap.get(cacheKey); if (Date.now() - timestamp < options.ttl) { console.log(`[${name}] 命中缓存`); return data; } console.log(`[${name}] 缓存过期,重新计算`); cacheMap.delete(cacheKey); } const result = original.apply(this, args); cacheMap.set(cacheKey, { data: result, timestamp: Date.now() }); return result; }; return descriptor; }; }
class FibCalculator { @cache({ ttl: 5000 }) fib(n) { console.log(`[fib] 计算 fib(${n})`); if (n <= 1) return n; return this.fib(n - 1) + this.fib(n - 2); } }
const calculator = new FibCalculator(); calculator.fib(10); calculator.fib(10); setTimeout(() => calculator.fib(10), 6000);
|
4. 防抖与节流
装饰事件处理方法(如输入框搜索、按钮点击、窗口resize),避免频繁触发,提升用户体验。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| function debounce(delay = 300, immediate = false) { let timer = null; return function (target, name, descriptor) { const original = descriptor.value; descriptor.value = function (...args) { clearTimeout(timer); if (immediate && !timer) { original.apply(this, args); } timer = setTimeout(() => { original.apply(this, args); timer = null; }, delay); }; return descriptor; }; }
function throttle(interval = 500) { let lastTime = 0; return function (target, name, descriptor) { const original = descriptor.value; descriptor.value = function (...args) { const now = Date.now(); if (now - lastTime >= interval) { lastTime = now; original.apply(this, args); } }; return descriptor; }; }
class SearchComponent { @debounce(500) handleSearch(keyword) { console.log("搜索关键词:", keyword); } @throttle(1000) handleScroll() { console.log("窗口滚动"); } }
|
5. 错误捕获与统一处理
捕获方法执行中的错误,避免程序崩溃,并统一进行错误上报、用户提示等处理,减少重复的 try/catch 代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| function errorHandler(cb = (err) => { console.error("发生错误:", err); alert("操作失败,请重试"); }) { return function (target, name, descriptor) { const original = descriptor.value; descriptor.value = async function (...args) { try { return await original.apply(this, args); } catch (err) { cb(err, { method: name, args }); } }; return descriptor; }; }
class APIService { @errorHandler(async (err, info) => { await fetch("/api/error-report", { method: "POST", body: JSON.stringify({ message: err.message, stack: err.stack, method: info.method, args: info.args, time: new Date().toLocaleString() }) }); alert("网络异常,请稍后再试"); }) async getUserInfo(userId) { const response = await fetch(`/api/users/${userId}`); if (!response.ok) throw new Error(`HTTP 错误:${response.status}`); return response.json(); } }
|
6. 框架集成(React/Vue)
在类组件中复用框架能力,简化代码。例如 React 中连接 Redux、路由守卫,Vue 中装饰组件选项等。
React 中连接 Redux(react-redux)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import React from "react"; import { connect } from "react-redux"; import { increment, decrement } from "./store/actions";
@connect( state => ({ count: state.counter.count }), { increment, decrement } ) class Counter extends React.Component { render() { const { count, increment, decrement } = this.props; return ( <div> <p>计数:{count}</p> <button onClick={increment}>+1</button> <button onClick={decrement}>-1</button> </div> ); } }
export default Counter;
|
Vue 中装饰组件(vue-class-component)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import Vue from "vue"; import Component from "vue-class-component"; import { Prop } from "vue-property-decorator";
@Component({ template: ` <div> <p>{{ message }}</p> <button @click="sayHello">点击</button> </div> ` }) export default class HelloWorld extends Vue { @Prop({ type: String, default: "Hello Vue" }) message!: string; sayHello() { console.log(this.message); } }
|
使用 Decorator 的注意事项与避坑指南
提案兼容性问题
2025 年 Decorator 仍处于 TC39 Stage 3 阶段,不同工具链的配置需适配最新语。
装饰器的执行顺序
多个装饰器叠加时,执行顺序为 “从右到左、从上到下(外层装饰器包裹内层装饰器)
1 2 3 4 5 6
| @decoratorA @decoratorB @decoratorC class MyClass {}
|
this 上下文绑定
方法装饰器中,原方法的 this 默认指向 undefined(严格模式下),需通过 original.apply(this,args) 或者original.call(this, ...args) 绑定当前实例的 this,否则会导致 this 丢失。
不可装饰普通函数
当前 Stage 3 提案仅支持装饰“类”和“类成员”,不支持直接装饰普通函数。若需扩展普通函数,可使用高阶函数替代:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function logFunc(fn) { return function (...args) { console.log("调用参数:", args); return fn.apply(this, args); }; }
function add(a, b) { return a + b; }
const addWithLog = logFunc(add); addWithLog(1, 2);
|
写在最后
Decorator 并非 ES6 正式标准,但它已成为前端开发中“非侵入式扩展”的经典方案,其核心价值在于分离通用逻辑与业务逻辑,实现代码复用,同时使得代码更加优雅。
如果你在开发中面临“通用逻辑重复编写”“业务代码被辅助逻辑侵入”等问题,不妨尝试使用 Decorator——它会让你的代码更简洁、更优雅、更具扩展性。
【往期精彩】