最近在学习 NestJs
,也可以理解为我要开始接触后端了,在这段你的时间里我接触到了很多新的概念,例如控制反转和依赖注入,NestJs
大量借鉴了 Angular
和 spring
的思想,所以这也就是一些 vue
或者 react
开发者开始学习 NestJs
会有些不适应,它通过 @Controller
、@Injectable
装饰器声明的类会被 NestJs
扫描,创建对应的对象并添加到一个容器里,最终形成一个类似于 Webpack
模块图一样,这些所有的对象会根据构造器里声明的依赖自动注入,也就是 DI(dependency Inject)
,这种思想叫做 IOC(Inverse Of Control)
,后者就是控制反转,是一种设计模式,而前者就是依赖注入,是一种具体的实现方式。
在官方文档中有这样的一句话:
Nest is built around the strong design pattern commonly known as Dependency injection. We recommend reading a great article about this concept in the official Angular documentation.
现在 JavaScript
中的装饰器提案已经进入到 stage 3
阶段了,相信不久的将来就算你不用ts也可以使用上了,赶紧学起来吧!!!
装饰器(Decorator)
装饰器是一种特殊的声明,可以附加到类声明、方法、访问器、属性或参数上,装饰者使用 @+函数名
形式来修改类的行为。
常见的装饰器有:
类装饰器
;属性装饰器
;方法装饰器
;参数装饰器
;
而装饰器的写法又分为两种它们分别是:
普通装饰器(无法传参)
;装饰器工厂(可传参)
;
类装饰器(普通装饰器)
类装饰器声明在类声明之前,类装饰器应用于类的构造函数,可用于观察、修改或替换来定义,它不能在声明文件中使用,也不能在任何其他环境上下文中使用(例如在声明类上)。
具体代码如下所示:
function Controller(params: any) {
console.log(params === Services); // true
params.prototype.moment = "777";
}
@Controller
class Services {
constructor() {}
getMoment() {}
}
const s: any = new Services();
console.log(s.moment); // 777
在上面的代码中,我们可以得出以下结论:
Controller
装饰器声明在类声明之前,并且不携带分号;- 在
Controller
函数中的参数params
就是Services
类; - 可以在不修改类的前提上扩展类和方法;
在上面的例子中,它是一个普通的装饰器,它无法传参,传参会报错:
类装饰器(装饰器工厂)
如果你想定义一个装饰器工厂,那么它很简单,它也是一个函数,和前面的不同的是,它又返回一个函数,这个被返回的函数接收一个参数,这个参数就是被装饰的类,具体代码如下所示:
function Controller(params: string) {
console.log(params); // moment
return function (target) {
console.log(target === Services); // true
target.prototype[params] = 777;
};
}
@Controller("moment")
class Services {
constructor() {}
getMoment() {}
}
const s: any = new Services();
console.log(s.moment); // 777
而函数 Controller
中的 params
参数正是调用装饰器时传入的参数。
属性装饰器
属性装饰器就比较简单了,直接上代码吧:
function Controller(params) {
return function (target: any, attribute: any) {
console.log(target);
console.log(attribute);
console.log(params);
};
}
class Services {
@Controller("nba")
public moment: number;
}
const s: any = new Services();
最终的输出结果如下图所示:
方法装饰器
方法装饰器的表达式将在运行时作为函数调用,带有以下三个参数:
- 静态成员类的构造函数或者实例成员类的原型;
- 成员的名称;
- 成员的属性描述符;
先看以下代码:
function Controller(params) {
return function (target: any, attribute: any, descriptor: any) {
console.log(target);
console.log(attribute);
console.log(descriptor);
};
}
class Http {
constructor() {}
@Controller("t")
getDate() {}
}
最终的输出结果如下图所示:
在这里我们可以通过 descriptor.value
来获取当前被装饰的方法,在这里我可以对该方法进行修改或者拦截,具体代码如下所示:
function Controller(params: any) {
return function (target: any, attribute: any, descriptor: any) {
const method = descriptor.value;
descriptor.value = function (value: string) {
method.call(this, value);
return value.toLocaleUpperCase();
};
};
}
class Http {
constructor() {}
@Controller("sss")
getDate() {
console.log(666666);
}
}
const foo: any = new Http();
console.log(foo.getDate("nba"));
在这段代码中,方法装饰器修改了当前的方法,对方法进行扩展,通过 method
来对当前方法进行保存,最后进行调用目的是为了不改变原有的函数体。
方法参数装饰器
方法参数装饰器表达式会在运行时当做函数被调用,可以使用参数装饰器为类的原型增加一些元素数据,传入下列的三个参数:
- 静态成员类的构造函数或者实例成员类的原型;
- 方法的名称;
- 参数在函数参数列表中的索引;
具体代码如下所示:
function Controller(params: any) {
return function (target: any, method: any, index: number) {
console.log(params); // cba
console.log(target); // Http 类
console.log(method); // 方法名
console.log(index); // 参数索引
};
}
class Http {
constructor() {}
getDate(@Controller("cba") value: any) {
console.log(value); // nba
}
}
const foo: any = new Http();
foo.getDate("nba");
最终的输出结果如下图所示:
装饰器执行顺序
装饰器的执行顺序很简单,但是我看到网上的讲解有说是这样的顺序的,如下:
- 属性装饰器;
- 方法参数装饰器;
- 方法装饰器;
- 类装饰器;
其实这个说法是错误的,装饰器的执行顺序是谁先执行完就先调用谁(方法参数是从后到前,方法也是),具体代码如下所示:
function a(params: any) {
console.log("类装饰器");
}
function b(params: any) {
return function (target: any, name: any) {
console.log("属性装饰器");
};
}
function c(params: any) {
return function (target: any, name: any) {
console.log("方法装饰器");
};
}
function d(params: any) {
return function (target: any, name: any, index: number) {
console.log("方法参数装饰器");
};
}
function e(params: any) {
return function (target: any, name: any, index: number) {
console.log("方法参数装饰器1");
};
}
@a
class Http {
constructor() {}
@c("")
getDate(@d("") value, @e("") v: any) {}
@b("")
public moment: number | undefined;
}
const foo: any = new Http();
foo.getDate("nba");
这段代码的最终输出结果如下所示:
看,现在不是属性装饰器执行完了吧,这个主要是看 JavaScript
代码从上往下执行,谁先执行完就调用谁,在整段代码中,整个类最后执行完,这也就是类最后调用的原因了。
参考文献
结尾
很久之前就看到了一句很好的一句话,现在突然想起来,现在分享给大嘎:
做人要接受自己的世俗,然后你会发现你坚定的不一定是真,你崇尚的不一定是善,你热爱的不一定是美,你的才华并不能撑起你满腔的抱负,你并不是世界的中心,你的征途也并不是星辰大海,你只是个平庸且世俗的人,你唯一能做的,就是珍惜并抓住眼前的所有,然后美其名曰幸福。
本文正在参加「金石计划」