用过 Java 的同学可能熟悉注解(Annotation)。在 Java 中可以通过反射获取注解内容,从而加强注解标注的代码。最常用的注解莫过于 @Override
,它用于标注方法是否是重写方法。SpringBoot 的许多配置也从最初的 XML 转向注解。
JavaScript 也有类似 Java 注解的功能,不过在 JavaScript 中其称作装饰器( Decorator),目前该提案处于 Stage 3。如下图,该语法趋于完善。
(TC39 提案交付流程)
提案中对装饰器的正式定义是:
Decorators are functions called on classes, class elements, or other JavaScript syntax forms during definition.
装饰器是在定义过程中,在类、类元素或其他 JavaScript 语法单位上调用的函数。
装饰器有啥用呢?所谓装饰器就是在原先的基础上增加一些功能。
来看官方给的一个例子,装饰器 @logged
装饰类 C
的方法 m
。达到的效果就是当 m
被调用,自动输出日志。
function logged(value, { kind, name }) {
if (kind === "method") {
return function (...args) {
console.log(`starting ${name} with arguments ${args.join(", ")}`);
const ret = value.call(this, ...args);
console.log(`ending ${name}`);
return ret;
};
}
}
class C {
@logged
m(arg) {}
}
new C().m(1);
// starting m with arguments 1
// ending m
再来看一个真实的例子,下面的代码都来自项目 cnpmcore。cnpmcore 是使用 tegg 框架搭建的,其中使用了许多装饰器,熟悉 SpringBoot 的同学应该会对该项目感到亲切。
下面的例子是通过装饰器 @Middleware
给类 MiddlewareController
加上了三个中间件 AlwaysAuth
、Tracing
和 ErrorHandler
。此处达到的效果是在调用 MiddlewareController
及其子类的方法时(自调用不算),会依次经过 AlwaysAuth
、Tracing
和 ErrorHandler
。
import { Middleware } from '@eggjs/tegg';
import { AlwaysAuth } from './AlwaysAuth';
import { ErrorHandler } from './ErrorHandler';
import { Tracing } from './Tracing';
@Middleware(AlwaysAuth)
@Middleware(Tracing)
@Middleware(ErrorHandler)
export abstract class MiddlewareController {}
(src:/app/port/middleware/index.ts)
例如 AlwaysAuth
就用于判断用户是否有权限访问,若无直接拦截请求。
import { EggContext, Next } from '@eggjs/tegg';
import { UserRoleManager } from '../UserRoleManager';
export async function AlwaysAuth(ctx: EggContext, next: Next) {
if (ctx.app.config.cnpmcore.alwaysAuth) {
// ignore login request: `PUT /-/user/org.couchdb.user::username`
const isLoginRequest = ctx.method === 'PUT' && ctx.path.startsWith('/-/user/org.couchdb.user:');
if (!isLoginRequest) {
const userRoleManager = await ctx.getEggObject(UserRoleManager);
await userRoleManager.requiredAuthorizedUser(ctx, 'read');
}
}
await next();
}
(src:/app/port/middleware/AlwaysAuth.ts)
更多实战例子请移步 cnpmcore,比如通过注解定义持久层。
mport { Attribute, Model } from '@eggjs/tegg/orm';
import { DataTypes, Bone } from 'leoric';
@Model()
export class Change extends Bone {
@Attribute(DataTypes.BIGINT, {
primary: true,
autoIncrement: true,
})
id: bigint;
@Attribute(DataTypes.DATE, { name: 'gmt_create' })
createdAt: Date;
@Attribute(DataTypes.DATE, { name: 'gmt_modified' })
updatedAt: Date;
@Attribute(DataTypes.STRING(24), {
unique: true,
})
changeId: string;
@Attribute(DataTypes.STRING(50))
type: string;
@Attribute(DataTypes.STRING(214))
targetName: string;
@Attribute(DataTypes.JSONB)
data: any;
}
(src:/app/repository/model/Change.ts)
总结,无论是 Java 中的注解,还是 JavaScript 中的装饰器都属于元编程的范畴。它的强大之处在于一个装饰器就可以引入许多重复、复杂的逻辑,比如 SpringBoot 中的注解已经将编程难度极大地降低。但不好之处在于元编程的代码可读性对初学者不太友好,有时很难看出某个装饰器到底干了啥。
在可预见的将来,JavaScript 语法会变得更强大。对 Java 程序员或许更友好,出现一个框架让他们可以像写 SpringBoot 一样用 JavaScript / TypeScript 编写后端代码(tegg 似乎是一个选择)。