8. 你是怎么理解ES6中 Decorator 的?使用场景?
一、面试开场怎么答更有层次如果面试官问你怎么理解 ES6 中的 Decorator有什么使用场景不要直接说装饰器就是在类上面加个注解。更好的开场方式是从它的本质说起Decorator装饰器是一种用于修改类、方法、属性行为的语法糖本质上是一个函数。它的核心思想来自设计模式里的装饰器模式允许在不改变原有代码的情况下通过外层包装来扩展或修改目标的行为。它让代码可以以一种声明式的方式把横切关注点比如日志、权限、缓存从业务逻辑中分离出来。这个开场就很有深度。二、什么是 Decorator1本质是函数装饰器本质上就是一个函数接收被装饰的目标作为参数对其进行处理后返回。function log(target) { console.log(装饰了, target) return target } log class MyClass {}2目前的状态说明这个面试里一定要主动提会显得你很严谨Decorator 目前还不是正式的 ES 标准特性严格来说不属于 ES6。它长期处于 TC39 提案阶段历经多次重大改版旧版Stage 1/2Babel TypeScript 早期支持的版本大量框架在用新版Stage 32022 年后推进的新提案语义有所调整目前实际开发中用的装饰器大多数是通过 Babel 或 TypeScript 编译支持的旧版方案。面试加分说法虽然 Decorator 还不是正式标准但在工程实践里已经非常成熟Angular、NestJS、MobX、TypeScript 都大量使用所以面试里讲装饰器是完全合理的。3如何在项目中使用通过 Babel 插件支持npm install babel/plugin-proposal-decorators.babelrc{ plugins: [ [babel/plugin-proposal-decorators, { legacy: true }] ] }TypeScript 中开启{ compilerOptions: { experimentalDecorators: true } }三、Decorator 的类型1类装饰器作用于整个类接收类的构造函数作为参数。function sealed(target) { Object.seal(target) Object.seal(target.prototype) } sealed class Person { constructor(name) { this.name name } }用途给类添加静态属性或方法修改类的行为给类打标记可以返回新类function addVersion(target) { return class extends target { get version() { return 1.0.0 } } } addVersion class App {} const app new App() console.log(app.version) // 1.0.02方法装饰器作用于类的方法接收三个参数target类的原型对象name方法名descriptor属性描述符function log(target, name, descriptor) { const original descriptor.value descriptor.value function(...args) { console.log(调用 ${name}参数, args) const result original.apply(this, args) console.log(${name} 返回, result) return result } return descriptor } class Calculator { log add(a, b) { return a b } } const calc new Calculator() calc.add(1, 2) // 调用 add参数[1, 2] // add 返回3面试加分点方法装饰器本质上是修改方法的属性描述符descriptor和Object.defineProperty的原理是一样的。3属性装饰器作用于类的属性。function readonly(target, name, descriptor) { descriptor.writable false return descriptor } class Person { readonly name Tom }4访问器装饰器作用于getter/setter。function configurable(value) { return function(target, name, descriptor) { descriptor.configurable value return descriptor } } class Person { configurable(false) get name() { return Tom } }5参数装饰器作用于方法的参数TypeScript 中常见。function required(target, name, index) { // 标记某个参数是必须的 } class Service { greet(required name) { return Hello ${name} } }四、装饰器工厂带参数的装饰器装饰器本身可以接受参数这时需要返回一个函数function log(prefix) { return function(target, name, descriptor) { const original descriptor.value descriptor.value function(...args) { console.log([${prefix}] 调用 ${name}) return original.apply(this, args) } return descriptor } } class Service { log(API) fetchData() { // ... } }面试加分说法装饰器工厂的本质就是一个高阶函数外层函数接收配置参数内层函数才是真正的装饰器。五、多个装饰器的执行顺序这是面试里的高频考点。function dec1(target) { console.log(dec1 执行) return target } function dec2(target) { console.log(dec2 执行) return target } dec1 dec2 class MyClass {}输出顺序dec2 执行 dec1 执行规律装饰器的求值顺序从上到下先 dec1再 dec2装饰器的执行顺序从下到上先 dec2再 dec1面试加分说法多个装饰器的执行顺序类似函数组合compose从内到外执行最靠近类的装饰器最先执行。这和数学里的f(g(x))是一样的先算g(x)再算f。六、使用场景这是面试里最想听的内容要结合实际说。1日志记录function log(target, name, descriptor) { const fn descriptor.value descriptor.value function(...args) { console.log(${name} called with, args) return fn.apply(this, args) } return descriptor } class UserService { log getUser(id) { // ... } }2权限控制function requireAuth(target, name, descriptor) { const fn descriptor.value descriptor.value function(...args) { if (!currentUser.isLoggedIn) { throw new Error(未登录无权访问) } return fn.apply(this, args) } return descriptor } class AdminService { requireAuth deleteUser(id) { // ... } }3缓存Memoizefunction memoize(target, name, descriptor) { const fn descriptor.value const cache new Map() descriptor.value function(...args) { const key JSON.stringify(args) if (cache.has(key)) { return cache.get(key) } const result fn.apply(this, args) cache.set(key, result) return result } return descriptor } class Calculator { memoize heavyCompute(n) { // 复杂计算 } }4节流 / 防抖function throttle(ms) { return function(target, name, descriptor) { const fn descriptor.value let lastTime 0 descriptor.value function(...args) { const now Date.now() if (now - lastTime ms) { lastTime now return fn.apply(this, args) } } return descriptor } } class Search { throttle(500) handleInput(e) { // 搜索逻辑 } }5只读属性function readonly(target, name, descriptor) { descriptor.writable false return descriptor } class Config { readonly version 1.0.0 }6数据验证function validate(target, name, descriptor) { const fn descriptor.value descriptor.value function(value) { if (typeof value ! number) { throw new TypeError(参数必须是数字) } return fn.apply(this, [value]) } return descriptor } class MathHelper { validate double(n) { return n * 2 } }7框架中的典型应用这部分会让面试官觉得你有实际经验。AngularComponent({ selector: app-root, template: h1Hello/h1 }) export class AppComponent {} Injectable() export class UserService {}NestJSController(users) export class UsersController { Get(:id) findOne(Param(id) id: string) {} }MobXclass Store { observable count 0 action increment() { this.count } computed get double() { return this.count * 2 } }TypeORMEntity() class User { PrimaryGeneratedColumn() id: number Column() name: string }面试加分说法在框架层面装饰器的价值在于把元数据和配置信息以声明式的方式写在类上让代码更简洁框架底层通过反射读取这些元数据来完成注入、路由、验证等功能。七、Decorator 的核心价值这是面试里画龙点睛的部分。1关注点分离把日志、权限、缓存等横切关注点从业务代码中分离出来。2声明式编程用注解的方式表达意图而不是命令式地写具体步骤。3代码复用装饰器是可复用的同一个装饰器可以应用到多个类或方法上。4不修改原代码符合开放封闭原则对扩展开放对修改关闭。八、Decorator 和设计模式的关系这个能讲出来非常加分。装饰器语法的背后是面向对象设计模式里的装饰器模式Decorator Pattern。装饰器模式的核心思想是在不修改原对象的前提下通过包装的方式动态地给对象添加新的行为。ES6 的 Decorator 语法本质上就是这个设计模式的语言层面实现。九、面试标准回答我理解 Decorator装饰器是一种特殊的语法本质上是一个函数用于修改类、方法、属性或参数的行为。它的核心思想来自设计模式里的装饰器模式在不改变原有代码的情况下通过外层包装来扩展或修改目标的行为。需要说明的是Decorator 目前还不是正式的 ES 标准而是长期处于 TC39 提案阶段现在主要通过 Babel 或 TypeScript 的编译支持来使用。从类型上看装饰器可以作用在类、方法、属性、访问器和参数上。类装饰器接收构造函数可以修改类的行为或返回新类方法装饰器接收类的原型、方法名和属性描述符本质上是对Object.defineProperty的封装属性装饰器可以控制属性的可写性等特性。装饰器还支持带参数的装饰器工厂写法本质上是高阶函数。多个装饰器的执行顺序是从下到上类似函数组合。使用场景方面我觉得最典型的有日志记录、权限控制、缓存、节流防抖、数据验证、只读属性等。在框架层面Angular 的Component、NestJS 的Controller、MobX 的observable、TypeORM 的Entity都是装饰器的典型应用。这些框架利用装饰器把元数据绑定到类上通过反射机制实现依赖注入、路由注册等功能。装饰器的核心价值在于关注点分离、声明式编程、代码复用以及符合开放封闭原则。它让横切关注点日志、权限、缓存可以从业务逻辑中剥离出来以一种优雅的方式复用。十、精简版面试回答Decorator 装饰器本质上是一个函数用来修改类、方法、属性等的行为不需要改动原有代码。它来自设计模式的装饰器模式通过 Babel 或 TypeScript 编译支持使用目前还在 TC39 提案阶段。类型上有类装饰器、方法装饰器、属性装饰器等方法装饰器本质上是对属性描述符的修改。多个装饰器执行顺序是从下到上装饰器工厂是高阶函数写法。常见场景有日志、权限、缓存、防抖、数据验证框架层面 Angular、NestJS、MobX 都大量使用。核心价值是关注点分离、声明式编程、复用性强符合开放封闭原则。十一、如果想答得更高级可以补这几句1Decorator 和 AOP 的关系装饰器是实现 AOP面向切面编程的一种方式。AOP 的核心是把横切关注点日志、事务、权限从主业务流程中分离出来在不侵入业务代码的情况下动态织入。装饰器正好可以在方法执行前后插入逻辑这和 AOP 的切面概念完全吻合。2Decorator 和反射元数据的关系在 TypeScript 中装饰器常配合reflect-metadata库使用。装饰器可以在类或方法上写入元数据框架在运行时通过Reflect.getMetadata读取这些元数据从而实现依赖注入、参数解析等功能。这是 Angular、NestJS 等框架底层的核心机制。3新版 Decorator 提案的变化2022 年后推进的新版 Decorator 提案Stage 3和旧版相比有较大变化比如方法装饰器不再接收 descriptor而是接收一个 context 对象支持了私有字段装饰器语义更加严格和清晰。但目前大多数实际项目用的还是旧版方案。十二、一句话总结面试官真正想听的是你是否知道Decorator 的本质是函数且还在提案阶段你能不能说清楚类装饰器、方法装饰器的区别和参数你是否理解方法装饰器本质上是操作属性描述符你是否知道多个装饰器的执行顺序你能不能结合框架层面的实际应用来讲你有没有意识到装饰器是关注点分离和 AOP 的实现方式