网站优化工作安排,wordpress版本查询,永清建设局网站,开发公司运行管理情况建议及意见JavaScript 装饰器#xff08;Decorator#xff09;是 ES7 提案中的特性#xff0c;核心是通过“包装目标对象”#xff0c;在不修改原对象源码的前提下#xff0c;动态扩展其功能#xff0c;本质是“高阶函数的语法糖”#xff0c;让代码复用、功能增强更简洁优雅…JavaScript 装饰器Decorator是 ES7 提案中的特性核心是通过“包装目标对象”在不修改原对象源码的前提下动态扩展其功能本质是“高阶函数的语法糖”让代码复用、功能增强更简洁优雅已广泛应用于 React、Vue3、Node.js 等主流技术栈。一、装饰器核心原理1. 底层逻辑装饰器本质是「接收目标对象、返回新对象或修改原对象的高阶函数」核心流程拦截目标对象类、方法、属性等的定义/创建过程对目标对象进行功能增强如添加日志、权限校验、缓存等返回增强后的对象替换原目标对象生效。2. 语法本质去糖示例装饰器的xxx语法是简化写法底层可手动拆解为高阶函数调用清晰理解原理// 1. 定义装饰器本质是高阶函数functionlogDecorator(target,name,descriptor){constoriginFndescriptor.value;// 保存原方法descriptor.valuefunction(...args){// 重写方法添加增强逻辑console.log(调用方法${name}参数${args});constresultoriginFn.apply(this,args);// 执行原方法console.log(方法${name}执行完成返回值${result});returnresult;};returndescriptor;// 返回修改后的描述符}// 2. 装饰器语法 糖衣classUser{logDecoratoradd(name){return添加用户${name};}}// 3. 去糖后等价于手动调用高阶函数classUserOrigin{add(name){return添加用户${name};}}// 手动获取方法描述符调用装饰器重新定义方法constdescriptorObject.getOwnPropertyDescriptor(UserOrigin.prototype,add);constnewDescriptorlogDecorator(UserOrigin.prototype,add,descriptor);Object.defineProperty(UserOrigin.prototype,add,newDescriptor);// 调用验证两种写法效果完全一致newUser().add(张三);// 输出日志 返回结果newUserOrigin().add(李四);// 输出日志 返回结果3. 核心概念属性描述符descriptor装饰器操作的核心是「对象属性描述符」方法/属性的装饰器会接收descriptor参数其关键属性value目标方法/属性的值方法装饰器核心操作此属性writable是否可修改默认trueenumerable是否可枚举默认false类方法默认不可遍历configurable是否可删除/修改描述符默认false。二、装饰器的分类含使用场景实例根据装饰目标不同分为 5 大类类装饰器、方法装饰器、属性装饰器最常用优先掌握1. 类装饰器装饰整个类作用给类添加静态属性/方法修改类的构造函数逻辑扩展类的实例功能如混入 Mixin。语法装饰器函数仅接收 1 个参数target目标类本身返回值为「新类」或「修改后的原类」。实战场景 1给类添加静态属性// 装饰器给类添加「版本号」「创建时间」静态属性functionaddStaticInfo(target){target.version1.0.0;// 静态属性target.createTimenewDate().toLocaleString();// 静态属性target.getInfofunction(){// 静态方法return版本${this.version}创建时间${this.createTime};};returntarget;// 返回修改后的类}// 使用装饰器addStaticInfoclassUserService{getUser(){return{id:1,name:张三};}}// 验证效果console.log(UserService.version);// 1.0.0console.log(UserService.getInfo());// 版本1.0.0创建时间xxx实战场景 2修改类的构造函数注入默认属性// 装饰器给实例注入默认的「状态属性」functioninjectDefaultState(defaultState){// 装饰器支持传参外层函数接收参数内层函数是真正的装饰器returnfunction(target){// 保存原构造函数constOriginClasstarget;// 定义新构造函数注入默认属性functionNewClass(...args){OriginClass.apply(this,args);// 执行原构造逻辑this.state{...defaultState,...this.state};// 合并默认状态}NewClass.prototypeObject.create(OriginClass.prototype);// 继承原型NewClass.prototype.constructorNewClass;returnNewClass;// 返回新类替换原类};}// 使用装饰器传入默认状态参数injectDefaultState({status:active,role:user})classUser{constructor(name){this.namename;// 实例可自定义 state会覆盖默认值this.state{role:admin};}}// 验证效果state 合并默认值自定义属性覆盖默认值constusernewUser(李四);console.log(user.state);// { status: active, role: admin }2. 方法装饰器装饰类的原型方法/静态方法作用日志打印调用前/后记录参数、返回值权限校验执行方法前判断权限缓存优化缓存方法返回结果避免重复计算异常捕获统一捕获方法执行错误。语法装饰器函数接收 3 个参数target原型方法 → 类的原型静态方法 → 类本身name目标方法的名称descriptor目标方法的属性描述符核心操作value。实战场景 1日志打印最常用// 装饰器记录方法调用日志支持传参是否打印返回值functionmethodLog(showResulttrue){returnfunction(target,name,descriptor){constoriginFndescriptor.value;descriptor.valueasyncfunction(...args){// 调用前日志conststartTimeDate.now();console.log([${newDate().toISOString()}] 方法${name}开始调用参数,args);letresult;try{resultawaitoriginFn.apply(this,args);// 支持异步方法// 调用成功日志if(showResult)console.log([${newDate().toISOString()}] 方法${name}调用成功返回值,result);console.log([${newDate().toISOString()}] 方法${name}执行耗时${Date.now()-startTime}ms);}catch(err){// 调用失败日志console.error([${newDate().toISOString()}] 方法${name}调用失败错误,err);throwerr;// 抛出错误不阻断业务逻辑}returnresult;};returndescriptor;};}classOrderService{// 装饰原型方法异步传参显示返回值methodLog(true)asynccreateOrder(price,goods){awaitnewPromise(resolvesetTimeout(resolve,100));// 模拟接口请求return{orderId:Date.now(),price,goods};}// 装饰静态方法传参不显示返回值methodLog(false)staticcancelOrder(orderId){if(!orderId)thrownewError(订单ID不能为空);returntrue;}}// 验证效果newOrderService().createOrder(99,[手机]);// 打印完整日志含返回值耗时OrderService.cancelOrder();// 打印错误日志抛出异常实战场景 2缓存优化避免重复计算// 装饰器缓存方法返回值key 为参数拼接支持基本类型参数functioncacheDecorator(){returnfunction(target,name,descriptor){constoriginFndescriptor.value;constcachenewMap();// 缓存容器key参数字符串value返回值descriptor.valuefunction(...args){constcacheKeyJSON.stringify(args);// 参数转字符串作为 key// 命中缓存直接返回if(cache.has(cacheKey)){console.log(方法${name}命中缓存参数,args);returncache.get(cacheKey);}// 未命中缓存执行原方法存入缓存constresultoriginFn.apply(this,args);cache.set(cacheKey,result);returnresult;};returndescriptor;};}classCalcService{// 装饰计算方法高耗时场景如大数据排序、复杂公式计算cacheDecorator()sum(a,b){console.log(执行 sum 计算${a}${b});returnab;}}constcalcnewCalcService();calc.sum(10,20);// 未命中执行计算输出日志 → 30calc.sum(10,20);// 命中缓存直接返回输出缓存日志 → 30calc.sum(30,40);// 未命中执行计算 → 703. 属性装饰器装饰类的原型属性/静态属性作用限制属性的取值/赋值规则如类型校验、范围限制给属性设置默认值监听属性变化类似 Vue 的 watch。语法装饰器函数接收 2 个参数无descriptor需手动获取/修改target原型属性 → 类的原型静态属性 → 类本身name目标属性的名称。实战场景属性类型校验防止赋值错误// 装饰器校验属性类型接收允许的类型数组如 [String, Number]functionvalidateType(allowTypes){returnfunction(target,name){letvalue;// 存储属性实际值// 重新定义属性通过 get/set 实现类型校验Object.defineProperty(target,name,{get(){returnvalue;// 取值时返回存储的值},set(newVal){// 校验新值类型是否在允许范围内constisLegalallowTypes.some(typenewValinstanceoftype);if(isLegal){valuenewVal;}else{consttypeNamesallowTypes.map(tt.name).join(/);console.error(属性${name}类型错误允许类型${typeNames}当前类型${newVal?.constructor?.name});}},enumerable:true,// 允许遍历属性configurable:true});};}classUser{// 原型属性仅允许 String 类型validateType([String])name;// 静态属性仅允许 Number 类型validateType([Number])staticageLimit;}// 验证效果constusernewUser();user.name张三;// 合法赋值成功user.name123;// 非法打印错误不赋值console.log(user.name);// 张三User.ageLimit18;// 合法赋值成功User.ageLimit18;// 非法打印错误不赋值console.log(User.ageLimit);// 184. 访问器装饰器装饰类的 get/set 方法作用增强 get 方法如返回值格式化增强 set 方法如赋值前数据清洗、权限校验。语法与方法装饰器一致接收target、name、descriptordescriptor含get取值函数和set赋值函数属性。实战场景属性赋值清洗去除字符串空格// 装饰器清洗字符串属性去除首尾空格空字符串转为 nullfunctiontrimString(target,name,descriptor){constoriginSetdescriptor.set;// 保存原 set 方法// 重写 set 方法添加清洗逻辑descriptor.setfunction(newVal){letcleanValnewVal;if(typeofnewValstring){cleanValnewVal.trim();// 去除首尾空格if(cleanVal)cleanValnull;// 空字符串转 null}originSet.call(this,cleanVal);// 执行原 set 方法};returndescriptor;}classProduct{constructor(){this._title;// 私有属性约定俗成}// 装饰访问器 set 方法trimStringsettitle(val){this._titleval;}gettitle(){returnthis._title||无标题;}}// 验证效果constproductnewProduct();product.title 手机 ;// 赋值带空格的字符串console.log(product.title);// 手机空格被去除product.title ;// 赋值全空格字符串console.log(product.title);// 无标题空字符串转 nullget 方法返回默认值5. 参数装饰器装饰方法的参数作用标记参数如标记“必填参数”校验参数合法性如参数非空、范围校验。语法装饰器函数接收 3 个参数target原型方法 → 类的原型静态方法 → 类本身name目标方法的名称index当前参数在方法参数列表中的索引从 0 开始。实战场景标记必填参数校验参数非空// 1. 存储必填参数的容器key方法名value必填参数索引数组constrequiredParamsnewMap();// 2. 参数装饰器标记参数为必填functionrequired(target,name,index){if(!requiredParams.has(name)){requiredParams.set(name,[]);}requiredParams.get(name).push(index);// 记录必填参数的索引}// 3. 方法装饰器校验必填参数需配合参数装饰器使用functioncheckRequired(target,name,descriptor){constoriginFndescriptor.value;descriptor.valuefunction(...args){// 获取当前方法的必填参数索引constrequiredIndexesrequiredParams.get(name)||[];// 校验每个必填参数是否为空for(constindexofrequiredIndexes){constparamargs[index];if(paramundefined||paramnull||param){thrownewError(方法${name}的第${index1}个参数为必填项不可为空);}}returnoriginFn.apply(this,args);};returndescriptor;}classLoginService{// 方法装饰器校验必填 参数装饰器标记必填checkRequiredlogin(required username,required password,rememberMefalse){console.log(登录用户名${username}记住密码${rememberMe});returntrue;}}// 验证效果constloginServicenewLoginService();loginService.login(admin,123456);// 合法执行成功loginService.login(,123456);// 非法第 1 个参数为空抛出错误loginService.login(admin,null);// 非法第 2 个参数为空抛出错误三、装饰器的核心作用解耦功能增强逻辑将日志、权限、缓存等通用功能与业务逻辑分离避免代码冗余如每个方法都写一遍日志代码复用性极高通用装饰器如日志、缓存可在全项目多个类/方法中复用减少重复开发动态扩展功能无需修改原代码通过添加/删除装饰器快速开启/关闭功能如测试环境加日志生产环境移除代码可读性提升xxx语法直观一眼能看出目标对象的增强逻辑如checkRequired即知方法有必填校验符合开闭原则对扩展开放新增装饰器增强功能对修改关闭不改动原业务代码。四、装饰器的设计思路落地核心1. 单一职责原则一个装饰器只做一件事如logDecorator只处理日志cacheDecorator只处理缓存避免装饰器逻辑臃肿便于复用和维护。2. 支持参数配置装饰器通过“外层函数接收参数内层函数实现逻辑”适配不同场景如methodLog(true)显示返回值methodLog(false)不显示。3. 兼容异步方法通过async/await处理异步方法确保增强逻辑如日志、异常捕获对同步/异步方法都生效参考方法装饰器的日志示例。4. 不破坏原逻辑装饰器需先保存原对象原类、原方法增强后通过apply/call执行原逻辑避免覆盖原功能核心“包装”而非“替换”。5. 可组合使用多个装饰器可叠加在同一目标上执行顺序为「从上到下定义从下到上执行」类似洋葱模型// 定义 3 个简单装饰器functiondecoratorA(target,name,descriptor){console.log(执行装饰器 A);returndescriptor;}functiondecoratorB(target,name,descriptor){console.log(执行装饰器 B);returndescriptor;}functiondecoratorC(target,name,descriptor){console.log(执行装饰器 C);returndescriptor;}classTest{// 装饰器顺序A → B → C定义顺序执行顺序C → B → AdecoratorA decoratorB decoratorCfn(){}}newTest().fn();// 输出执行装饰器 C → 执行装饰器 B → 执行装饰器 A五、实际项目中的应用方案前端Node.js1. 前端项目Vue3/React场景 1Vue3 组件装饰器配合vue-class-componentVue3 支持通过装饰器简化组件逻辑需安装依赖vue-class-componenttemplate div{{ username }} - {{ role }}/div /template script langts import { Component, Vue, Prop, Watch } from vue-class-component; // 自定义装饰器给组件添加权限判断逻辑 function checkRole(allowRoles: string[]) { return function(target: Vue, name: string, descriptor: PropertyDescriptor) { const originFn descriptor.value; descriptor.value function(...args) { const role this.role; // 组件实例的 role 属性 if (allowRoles.includes(role)) { originFn.apply(this, args); } else { alert(无权限执行此操作); } }; return descriptor; }; } Component export default class UserComponent extends Vue { // 属性装饰器定义 props类型校验默认值 Prop({ type: String, default: 游客 }) username!: string; role user; // 组件实例属性 // 访问器装饰器监听属性变化类似 Vue 的 watch Watch(username) onUsernameChange(newVal: string) { console.log(用户名变化, newVal); } // 方法装饰器权限校验 checkRole([admin, editor]) editUser() { console.log(编辑用户); } } /script场景 2React 组件装饰器高阶组件语法糖React 的高阶组件HOC可通过装饰器简化写法如withRouter、connectimport React from react; import { withRouter, RouteComponentProps } from react-router-dom; import { connect } from react-redux; // 自定义装饰器给组件添加加载状态 function withLoading(Component: React.ComponentType) { return function LoadingComponent(props: any) { if (props.loading) return div加载中.../div; return Component {...props} /; }; } // 装饰器组合使用路由注入 Redux 状态注入 加载状态 withRouter connect((state) ({ user: state.user, loading: state.loading })) withLoading class Home extends React.ComponentRouteComponentProps { render() { return div首页 - 用户名{this.props.user.name}/div; } } export default Home;2. Node.js 项目接口层/服务层场景 1接口请求日志Express/Koa 中间件装饰器给接口方法添加日志记录请求参数、响应结果、耗时// 装饰器接口请求日志functionapiLog(target,name,descriptor){constoriginFndescriptor.value;descriptor.valueasyncfunction(req,res,next){conststartTimeDate.now();console.log([API请求] 路径${req.path}方法${req.method}参数,req.body);try{constresultawaitoriginFn.apply(this,[req,res,next]);// 执行接口逻辑res.json({code:200,data:result});// 统一响应格式console.log([API响应] 路径${req.path}耗时${Date.now()-startTime}ms结果,result);}catch(err){res.json({code:500,msg:err.message});// 统一错误响应console.error([API错误] 路径${req.path}错误,err);next(err);}};returndescriptor;}// 服务层用户接口classUserController{// 装饰接口方法apiLogasyncgetUserList(req){const{page1,size10}req.query;// 业务逻辑查询数据库return{list:[{id:1,name:张三}],total:1};}apiLogasynccreateUser(req){const{name,age}req.body;if(!name)thrownewError(用户名必填);// 业务逻辑插入数据库return{id:Date.now(),name,age};}}// Express 路由注册constexpressrequire(express);constappexpress();constuserControllernewUserController();app.use(express.json());app.get(/api/users,userController.getUserList);app.post(/api/users,userController.createUser);app.listen(3000);场景 2数据库操作缓存Redis 装饰器给数据库查询方法添加 Redis 缓存减少数据库压力constredisrequire(redis);constclientredis.createClient({url:redis://localhost:6379});client.connect();// 装饰器Redis 缓存key 前缀参数拼接过期时间 5 分钟functionredisCache(prefixcache:,expire300){returnasyncfunction(target,name,descriptor){constoriginFndescriptor.value;descriptor.valueasyncfunction(...args){constcacheKey${prefix}${name}:${JSON.stringify(args)};// 先查 Redis 缓存constcacheDataawaitclient.get(cacheKey);if(cacheData){console.log(Redis 缓存命中key${cacheKey});returnJSON.parse(cacheData);}// 缓存未命中查数据库constdataawaitoriginFn.apply(this,args);// 存入 Redis设置过期时间awaitclient.setEx(cacheKey,expire,JSON.stringify(data));console.log(Redis 缓存存入key${cacheKey}过期时间${expire}s);returndata;};returndescriptor;};}// 数据层用户数据库操作classUserDao{// 装饰查询方法添加 Redis 缓存redisCache(user:)asyncselectUserById(id){console.log(查询数据库用户ID${id});// 模拟数据库查询return{id,name:张三,age:20};}}// 验证效果constuserDaonewUserDao();userDao.selectUserById(1);// 查数据库存入缓存userDao.selectUserById(1);// 查 Redis 缓存不查数据库六、浏览器兼容性方案装饰器是 ES7 提案原生浏览器不支持仅部分版本 Chrome 开启实验性标志支持需通过工具编译为 ES5/ES6 代码主流方案如下1. 核心编译工具Babel步骤 1安装依赖# 核心依赖Babel 预设 装饰器插件npminstallbabel/core babel/cli babel/preset-env babel/plugin-proposal-decorators babel/plugin-proposal-class-properties --save-dev步骤 2配置 Babel.babelrc 或 babel.config.json{presets:[[babel/preset-env,{targets: 0.25%, not dead,// 适配主流浏览器useBuiltIns:usage,// 自动引入 polyfillcorejs:3// core-js 版本处理 ES 新特性兼容}],],plugins:[// 装饰器插件必须放在 class-properties 前面[babel/plugin-proposal-decorators,{version:legacy}],// legacy 兼容旧语法[babel/plugin-proposal-class-properties,{loose:true}]// 支持类属性直接定义]}步骤 3编译代码# package.json 添加脚本scripts:{build:babel src --out-dir dist// 将 src 目录代码编译到 dist 目录}# 执行编译npmrun build2. 工程化项目集成Vue3/Vite/React场景 1Vue3 Vite无需额外配置 BabelVite 内置vitejs/plugin-vue-jsx插件支持装饰器需配置legacy: true// vite.config.jsimport{defineConfig}fromvite;importvuefromvitejs/plugin-vue;importvueJsxfromvitejs/plugin-vue-jsx;exportdefaultdefineConfig({plugins:[vue(),vueJsx({// 启用装饰器支持plugins:[[babel/plugin-proposal-decorators,{version:legacy}],[babel/plugin-proposal-class-properties,{loose:true}]]})]});场景 2React WebpackCreate React App 配置// webpack.config.jsmodule.exports{module:{rules:[{test:/\.(js|jsx|ts|tsx)$/,exclude:/node_modules/,use:{loader:babel-loader,options:{presets:[babel/preset-react,babel/preset-typescript],plugins:[[babel/plugin-proposal-decorators,{version:legacy}],[babel/plugin-proposal-class-properties,{loose:true}]]}}}]}};3. TypeScript 项目支持TS 原生支持装饰器只需在tsconfig.json中开启配置{compilerOptions:{target:ES6,// 目标版本experimentalDecorators:true,// 开启装饰器关键emitDecoratorMetadata:true,// 生成装饰器元数据可选如需要反射时开启module:ESNext,outDir:./dist},include:[src/**/*]}4. 兼容性注意事项装饰器提案仍在迭代legacy模式是目前最稳定的兼容方案避免使用2023-05等实验性版本低版本浏览器如 IE11需配合core-js3引入 polyfill处理Map、Promise等新特性避免在生产环境使用未编译的装饰器语法会导致浏览器报错。总结JavaScript 装饰器是“高阶函数的优雅封装”核心价值是「解耦、复用、动态扩展」重点掌握类装饰器、方法装饰器、属性装饰器的使用结合 Babel/TS 解决兼容性问题可在前端组件、接口日志、权限校验、缓存优化等场景大幅提升开发效率。实际项目中建议封装通用装饰器库如日志、缓存、权限统一团队使用规范减少重复开发。补充装饰器进阶实战与避坑指南一、通用装饰器工具库封装可直接落地基于高频场景封装 4 个通用装饰器支持多项目复用含完整注释与使用示例适配前端/Vue3/React/Node.js全场景1. 日志装饰器支持同步/异步、自定义前缀/** * 方法日志装饰器记录调用参数、返回值、耗时、错误信息 * param options 配置项 { prefix: 日志前缀, showResult: 是否显示返回值, showTime: 是否显示耗时 } */exportfunctionlog(options{}){const{prefix[LOG],showResulttrue,showTimetrue}options;returnfunction(target,methodName,descriptor){constoriginFndescriptor.value;// 适配同步/异步方法descriptor.valueasyncfunction(...args){conststartTimeDate.now();// 调用前日志console.log(${prefix}方法${methodName}开始调用参数,args);try{constresultawaitoriginFn.apply(this,args);// 调用成功日志if(showTime)console.log(${prefix}方法${methodName}执行耗时${Date.now()-startTime}ms);if(showResult)console.log(${prefix}方法${methodName}调用成功返回值,result);returnresult;}catch(error){// 调用失败日志console.error(${prefix}方法${methodName}调用失败错误,error);throwerror;// 抛出错误不阻断业务}};returndescriptor;};}2. 缓存装饰器支持过期时间、手动清除/** * 方法缓存装饰器缓存返回值避免重复计算/请求 * param options 配置项 { expire: 过期时间(ms, 0永久), cacheKeyFn: 自定义缓存key生成函数 } */exportfunctioncache(options{}){const{expire0,cacheKeyFn}options;constcacheMapnewMap();// 缓存容器key缓存keyvalue{ data: 数据, expireTime: 过期时间 }// 清除指定方法的缓存静态方法需手动调用cache.clearfunction(methodName,...args){constkeycacheKeyFn?cacheKeyFn(args):JSON.stringify(args);constfullKey${methodName}_${key};cacheMap.delete(fullKey);console.log([CACHE] 清除方法${methodName}的缓存key${fullKey});};returnfunction(target,methodName,descriptor){constoriginFndescriptor.value;descriptor.valueasyncfunction(...args){// 生成缓存key支持自定义生成逻辑constkeycacheKeyFn?cacheKeyFn(args):JSON.stringify(args);constfullKey${methodName}_${key};constcacheItemcacheMap.get(fullKey);// 缓存命中且未过期直接返回if(cacheItem){const{data,expireTime}cacheItem;if(expire0||Date.now()expireTime){console.log([CACHE] 方法${methodName}命中缓存key${fullKey});returndata;}// 缓存过期删除旧缓存cacheMap.delete(fullKey);}// 缓存未命中执行原方法constdataawaitoriginFn.apply(this,args);// 存入缓存计算过期时间constexpireTimeexpire0?Date.now()expire:Infinity;cacheMap.set(fullKey,{data,expireTime});console.log([CACHE] 方法${methodName}缓存存入key${fullKey}过期时间${expire||永久}ms);returndata;};returndescriptor;};}3. 权限校验装饰器支持角色/权限码校验/** * 权限校验装饰器执行方法前校验权限无权限则抛出错误/执行回调 * param options 配置项 { allowRoles: 允许的角色数组, allowPerms: 允许的权限码数组, noAuthCb: 无权限回调 } */exportfunctionauth(options{}){const{allowRoles[],allowPerms[],noAuthCb}options;if(allowRoles.length0allowPerms.length0){thrownewError([AUTH] 权限装饰器需配置 allowRoles 或 allowPerms);}returnfunction(target,methodName,descriptor){constoriginFndescriptor.value;descriptor.valuefunction(...args){// 假设从全局获取当前用户权限实际项目需从Vuex/Redux/全局状态获取constcurrentUser{role:user,perms:[user:view]};// 示例数据consthasRoleallowRoles.length0||allowRoles.includes(currentUser.role);consthasPermallowPerms.length0||allowPerms.some(permcurrentUser.perms.includes(perm));// 有权限执行原方法无权限执行回调/抛错if(hasRolehasPerm){returnoriginFn.apply(this,args);}else{if(typeofnoAuthCbfunction){noAuthCb(methodName,currentUser);return;}thrownewError([AUTH] 无权限执行方法${methodName}当前角色${currentUser.role}权限${currentUser.perms.join(,)});}};returndescriptor;};}4. 必填参数校验装饰器支持多参数标记/** * 标记参数为必填配合 requiredCheck 方法装饰器使用 */exportfunctionrequired(target,methodName,paramIndex){// 存储必填参数key方法名value必填参数索引数组if(!target.__requiredParams__){target.__requiredParams__newMap();}if(!target.__requiredParams__.has(methodName)){target.__requiredParams__.set(methodName,[]);}target.__requiredParams__.get(methodName).push(paramIndex);}/** * 必填参数校验装饰器校验标记为 required 的参数是否为空 */exportfunctionrequiredCheck(target,methodName,descriptor){constoriginFndescriptor.value;constrequiredParamstarget.__requiredParams__?.get(methodName)||[];descriptor.valuefunction(...args){for(constindexofrequiredParams){constparamargs[index];// 判定为空undefined/null/空字符串/空数组可根据需求调整constisEmptyparamundefined||paramnull||param||(Array.isArray(param)param.length0);if(isEmpty){thrownewError([PARAM] 方法${methodName}的第${index1}个参数为必填项不可为空);}}returnoriginFn.apply(this,args);};returndescriptor;}工具库使用示例Vue3 组件实战template button clickgetUserList(1, 10)获取用户列表/button button clickeditUser(1, 张三)编辑用户/button /template script setup langts import { log, cache, auth, required, requiredCheck } from /utils/decorators; class UserService { // 1. 日志缓存缓存5分钟显示耗时不显示返回值 log({ prefix: [USER-API], showResult: false, showTime: true }) cache({ expire: 5 * 60 * 1000 }) async getUserList(page: number, size: number) { // 模拟接口请求 await new Promise(resolve setTimeout(resolve, 200)); return { list: [{ id: 1, name: 张三 }], total: 100 }; } // 2. 权限校验必填参数仅admin角色可执行userName为必填 auth({ allowRoles: [admin], noAuthCb: (fnName) alert(无权限执行 ${fnName} 操作) }) requiredCheck editUser(userId: number, required userName: string) { console.log(编辑用户${userId} - ${userName}); } } const userService new UserService(); // 调用方法自动触发装饰器逻辑 const getUserList (page: number, size: number) userService.getUserList(page, size); const editUser (userId: number, userName: string) userService.editUser(userId, userName); /script二、装饰器在框架中的深度应用1. Vue3 Pinia 状态管理装饰器简化模块配合pinia-class-decorator库用装饰器定义 Pinia 模块简化语法// 安装依赖npm install pinia-class-decoratorimport{defineStore}frompinia;import{Store,State,Action,Getter}frompinia-class-decorator;import{log,cache}from/utils/decorators;// 装饰器定义 Pinia 模块StoreexportdefaultclassUserStoreextendsStore{// 状态等价于 state: () ({ ... })State()userInfo{id:,name:,role:user};State()token;// 计算属性等价于 getters: { ... }Getter()getisAdmin(){returnthis.userInfo.roleadmin;}// 动作等价于 actions: { ... }Action()setToken(newToken:string){this.tokennewToken;localStorage.setItem(token,newToken);}// 异步动作 日志装饰器Action()log({prefix:[PINIA-ACTION]})cache({expire:30*60*1000})// 缓存用户信息30分钟asyncfetchUserInfo(){// 模拟接口请求constresawaitfetch(/api/user/info).then(resres.json());this.userInfores.data;returnres.data;}}2. Node.js 接口分层装饰器统一处理跨域/限流在 Node.js 接口层用装饰器统一处理通用逻辑简化中间件配置// 1. 限流装饰器限制接口请求频率如10次/分钟exportfunctionrateLimit(options{max:10,windowMs:60*1000}){const{max,windowMs}options;constrequestMapnewMap();// keyIPvalue{ count: 请求次数, lastTime: 最后请求时间 }returnfunction(target,methodName,descriptor){constoriginFndescriptor.value;descriptor.valueasyncfunction(req,res,next){constclientIpreq.ip;// 获取客户端IPconstnowDate.now();constrequestInforequestMap.get(clientIp)||{count:0,lastTime:now};// 超出时间窗口重置请求次数if(now-requestInfo.lastTimewindowMs){requestInfo.count1;requestInfo.lastTimenow;}else{requestInfo.count;// 超出请求限制返回429if(requestInfo.countmax){returnres.status(429).json({code:429,msg:请求过于频繁请稍后再试});}}requestMap.set(clientIp,requestInfo);returnoriginFn.apply(this,[req,res,next]);};returndescriptor;};}// 2. 接口使用装饰器跨域限流日志classOrderController{cors()// 自定义跨域装饰器简化cors中间件rateLimit({max:5,windowMs:30*1000})// 5次/30秒log({prefix:[ORDER-API]})asyncgetOrderList(req,res){constordersawaitOrderModel.find({userId:req.query.userId});res.json({code:200,data:orders});}}三、装饰器避坑指南高频问题解决方案1. 装饰器执行顺序错误问题多个装饰器叠加时逻辑执行不符合预期原理装饰器执行顺序为「从上到下定义从下到上执行」洋葱模型示例A B C fn()→ 执行顺序C → B → A解决方案按「核心逻辑在下辅助逻辑在上」的顺序定义如先缓存再日志最后权限。2. 异步方法装饰器未处理 Promise问题异步方法的返回值/错误无法被装饰器捕获原因装饰器未用async/await处理原方法的 Promise解决方案装饰器内用async function包裹await originFn.apply(this, args)参考通用日志装饰器。3. 缓存装饰器参数为引用类型key 失效问题参数为对象/数组时JSON.stringify(args)可能生成相同 key如不同引用的空对象解决方案提供cacheKeyFn自定义 key 生成逻辑或用lodash.isEqual深比较参数// 自定义缓存key处理引用类型cache({cacheKeyFn:(args){const[user,page]args;return${user.id}_${page};// 用对象的唯一标识生成key}})asyncgetUserOrders(user,page){}4. TypeScript 装饰器提示“experimentalDecorators”警告问题TS 项目中使用装饰器编辑器提示实验性特性警告解决方案在tsconfig.json中开启experimentalDecorators: true和emitDecoratorMetadata: true参考前文兼容性配置。5. 装饰器修改原对象导致副作用问题装饰器直接修改原方法/属性导致其他地方使用原对象时逻辑异常原理装饰器应“包装”原对象而非直接修改解决方案先保存原对象const originFn descriptor.value增强后通过apply/call执行不直接覆盖原对象。四、装饰器未来趋势ES 标准进展目前装饰器处于ES2023 提案阶段Stage 3与早期legacy模式相比有 3 个核心变化装饰器返回值新标准装饰器返回「新对象」替换原对象而非修改descriptor类装饰器支持参数无需外层函数包裹可直接给类装饰器传参私有属性装饰支持装饰类的私有属性#privateProp。新标准装饰器示例未来语法// 类装饰器直接传参新标准injectDefaultState({status:active})classUser{}// 方法装饰器返回新方法新标准functionlog(target,methodName,{get,set}){return{get(){constfnget.call(this);returnfunction(...args){console.log(调用方法,methodName);returnfn.apply(this,args);};}};}兼容建议目前生产环境仍优先使用legacy模式Babel/TS 成熟支持待新标准稳定后可通过工具自动迁移语法。最终总结装饰器的核心是「无侵入式增强」通过封装通用逻辑实现代码的解耦与复用是前端/Node.js 项目中提升开发效率的关键技巧。掌握「类/方法/属性」三大核心装饰器结合通用工具库封装可快速落地到 Vue3/React/Node.js 项目中解决日志、缓存、权限等高频场景问题。实际开发中需注意装饰器执行顺序、异步处理、兼容性配置避开常见坑点同时关注 ES 标准进展逐步适配新语法。建议团队内部统一装饰器规范沉淀通用工具库最大化发挥装饰器的价值。