如今的前端领域已经从后端语言中汲取了众多设计模式和概念,使得前端开发早已超越了早期jQuery那种简单纯粹的时期。在面试中,我们时常需要提及这些新颖的术语和设计模式,以展现自己对现代前端技术的深入理解。接下来,我将分享一下自己对于装饰器(Decorator)、控制反转(IOC)以及依赖注入(DI)的认知,若有不当之处,还请各位专家指正。
控制反转IOC
控制反转是一种设计原则,其核心思想是将原本由代码直接操控的对象的调用权交给第三方(如IoC容器)来控制,以解耦代码,提高可维护性。在前端开发中,我们可以通过依赖注入等方式实现控制反转,使得组件之间的依赖关系更加清晰和灵活。
控制反转IoC(Inversion of Control)是说创建对象的控制权进行转移,以前创建对象的主动权和创建时机是由自己把控的,而现在这种权力转移到第三方,比如转移交给了IoC容器,它就是一个专门用来创建对象的工厂,你要什么对象,它就给你什么对象,有了 IoC容器,依赖关系就变了,原先的依赖关系就没了,它们都依赖IoC容器了,通过IoC容器来建立它们之间的关系。
依赖注入DI
依赖注入是一种设计模式,依赖注入是实现控制反转的一种具体方式。在依赖注入中,我们通过构造函数、属性或者方法等方式将依赖的对象或服务传递给需要它们的对象。这种方式有助于减少代码的耦合度,提高代码的可测试性和可维护性。
装饰器Decorator
首先要强调下这个是TypeScript上搞出来的并不是说javascript原生就支持。
装饰器模式允许我们动态地给一个对象添加一些额外的职责,就增加功能来说,装饰器模式相比生成子类更为灵活。在前端开发中,装饰器常用于增强类或者对象的功能,而不必修改其原有的代码。这种设计模式有助于提高代码的可维护性和复用性。
在语法上,装饰器有如下几个特征。
- 第一个字符(或者说前缀)是
@
,后面是一个表达式。 @
后面的表达式,必须是一个函数(或者执行后可以得到一个函数)。- 这个函数接受所修饰对象的一些相关值作为参数。
- 这个函数要么不返回值,要么返回一个新对象取代所修饰的目标对象。
理论总结
看了上面的名词解释可能你还是不知道在说什么,简单一点来说就是,装饰器是ts提供的一种语法,控制反转是为解耦代码,依赖注入是实现控制反转的一种实现方法。
上代码
正常的js代码
js
//a.js
class A {
constructor(count, delay) {
this.count = count;
this.delay = delay;
}
deploy(event) {
console.log(`${this.count}'eat----' ${event}`)
}
}
//b.js
class B {
constructor(dis, cy) {
this.dis = dis;
this.cy = cy;
}
start() {
console.log(`start`)
}
stop() {
console.log(`stop`)
}
}
// use.js
import A from './a.js';
import B from './b.js';
class Use {
constructor(count, delay, dis, cy) {
console.log('use');
this.a = new A(count, delay)
this.b = new B(dis, cy)
}
run(){
this.b.start()
}
}
//main.js
import Use from './use.js';
const main = new Use(1,2,3,4);
main.run()
上面是我们一般写代码时会出现的方式,这是没什么问题的功能都可以实现,他作为业务代码可以,做为框架或者公共项目是不能这样干的。 这个代码问题是如果我要给A加一个属性xx这个时候除了A自己的构造函数需要修改连带Use这个构造函数也需要修改对于维护就会变的麻烦和修改范围太多,所以要解决这个问题就需要改造代码这个改造就是用控制反转思想。
控制反转修改的代码
js
//a.js
class A {
constructor(count, delay) {
this.count = count;
this.delay = delay;
}
deploy(event) {
console.log(`${this.count}'eat----' ${event}`)
}
}
//b.js
class B {
constructor(dis, cy) {
this.dis = dis;
this.cy = cy;
}
start() {
console.log(`start`)
}
stop() {
console.log(`stop`)
}
}
// use.js
class Use {
constructor(a,b) {
console.log('use');
this.a = a;
this.b = b;
}
run(){
this.b.start()
}
}
//main.js
import A from './a.js';
import B from './b.js';
import Use from './use.js';
const a = new A(1, 2);
const b = new B(3, 4);
const main = new Use(a,b);
main.run()
这个代码现在如果要修改A或者B就不需要去修改Use了只需要在IoC容器就是main修改。
代码只是表达思想不做其他考量
如果我们代码用ts去写可以更加的简化
使用ts写代码
ts
//a.ts
class A {
constructor(public count:number, public delay:number) {
console.log('初始化A')
}
deploy(event:Event) {
console.log(`${this.count}'eat----' ${event}`)
}
}
//b.s
class B {
constructor(public dis:number, public cy:number) {
console.log('初始化B')
}
start() {
console.log(`start`)
}
stop() {
console.log(`stop`)
}
}
//use.ts
class Use {
constructor(public a:A,public b:B) {
console.log('初始化cUse')
}
run(){
this.b.start()
}
}
//main.js
import A from './a.ts';
import B from './b.ts';
import Use from './use.js';
const a = new A(1, 2);
const b = new B(3, 4);
const main = new Use(a,b);
main.run()
装饰器
就如上面的ts代码一样,在ts中实现依赖注入就会非常的简单,通过装饰器各大框架如nestjs,redi都实现了自己的依赖注入,这里就不说了大家自己去官方网站看吧。
对于装饰器了解更多大家可以看阮大神的文章装饰器
有了装饰器想修改类构造方法我们只需要返回一个函数就可以了
js
function countInstances(value:any, context:any) {
let instanceCount = 0;
const wrapper = function (...args:any[]) {
instanceCount++;
const instance = new value(...args);
instance.count = instanceCount;
return instance;
} as unknown as typeof MyClass;
wrapper.prototype = value.prototype; // A
return wrapper;
}
@countInstances
class MyClass {}
const inst1 = new MyClass();
inst1 instanceof MyClass // true
inst1.count // 1