preface
Decorator syntax is the syntax of the Stage2 phase of ECMA proposal TC39, because Stage2 means the draft phase and may change in the future. DefineProperty is a syntax that can be replaced by Object.defineProperty.
A Decorator is a class-related syntax for annotating or modifying classes and class methods. Many object-oriented languages have this feature, such as Java and Python. Python can decorate functions, but in JavaScript decorators can only decorate the parts related to classes.
A decorator
What is a decorator
Decorators are a new syntax related to classes for annotating or modifying classes and class methods. Decorator syntax is the syntax of the Stage2 phase of ECMA proposal TC39, because Stage2 means the draft phase and may change in the future. This article will explain in accordance with the current situation.
Many object-oriented languages have this feature, such as Java and Python. Python can decorate functions, but in JavaScript decorators can only decorate the parts related to classes.
The basic usage is that the @+ function name represents a decorator.
The nature of decorators
Of course, the above is the basic information of the decorator, not the nature of the decorator. A decorator is essentially a function with an @ sign. The purpose of this function is to add a layer of constraint logic to a class or its properties and methods, and this logic decouples the class from the function in the code representation. Take a look at the following example:
// Use decorators
@testable
class MyTestableClass {
// ...
}
function testable(target) {
target.isTestable = true;
}
// Execute the following code to print true
MyTestableClass.isTestable
// Do not use decorators
class MyTestableClass {
static isTestable = true
// ...
}
// Executing the following code also prints true
MyTestableClass.isTestable
Copy the code
In the code above, @testable is a testable testable testable testable testable device. It modifies the behavior of the MyTestableClass class by adding the static property isTestable to it. Testable functions target parameters from the MyTestableClass class itself. This time you can use the testable MyTestableClass class and that’s why it’s called testable.
It is also possible to write directly to MyTestableClass without using a decorator. Why write with decorator syntax? If you want to change isTestable to false in the future, you’ll need to go to each class and change it. But if you’re writing through a decorator, you’ll just need to change the implementation code of the decorator. This serves the purpose of decoupling.
How do decorators work
Decoration class
In addition to the above usage, you can also write decorator functions as higher-order functions to make decorators more flexible.
Such as:
function testable(isTestable) {
return function(target) { target.isTestable = isTestable; }}@testable(true)
class MyTestableClass {}
MyTestableClass.isTestable // true
@testable(false)
class MyClass {}
MyClass.isTestable // false
Copy the code
Decorates properties and methods
Write a component with a decorator:
@Component({
tag: 'my-component'.styleUrl: 'my-component.scss'
})
export class MyComponent {
@Prop() first: string;
@Prop() last: string;
@State() isVisible: boolean = true;
render() {
return (
<p>Hello, my name is {this.first} {this.last}</p>); }}Copy the code
As you can see, decorators also serve as comments, and the above components prop and state are clear.
If the same method has more than one decorator, it will be executed from the outside in and from the inside out, like peeling an onion.
The benefits of decorators
Using decorators makes it easy to implement AOP programming (slice-oriented programming) and decorator patterns.
AOP programming: At run time, the idea of cutting code dynamically into a class’s designated methods and locations is faceted programming. Interested can look at the answer www.zhihu.com/question/24… .
Decorator pattern: look at this article segmentfault.com/a/119000003… .
Dependency Injection
Dependency injection DI, inversion of control IOC, and IOC containers
Typically, since decorators are used to do some AOP programming, the dependency injection pattern is followed for complete decoupling.
First look at the code:
class B {}class A {
constructor(b: B) {
Class A depends on instances of class B}}// Inject an instance of B into a
const a = new A(new B());
Copy the code
When A class A relies on class B, but only uses class B without affecting class B or controlling the creation and destruction of class B, this is class B dependency injection into class A, where control of class B is not in class A but elsewhere is also called inversion of control (IOC).
In the above example, the dependency injection code of class B into class A was written by ourselves, that is, const A = new A(new B()). In actual projects, there may be many such classes that need to be injected, so we need an IOC container to manage the dependencies and behaviors in A unified way.
This explains the concept and relationship of DI/IOC/IOC containers
Benefits of dependency injection
A simple example is given with reference to some of the answers. Suppose you need to write code to assemble a car.
Dependency injection is not used
// The tire class
class Tire {}// Engine class
class Engine {
createEngine(){}}/ / class
class Car {
public engine: Engine;
public tires: Tires;
constructor() {
this.engine = new Engine();
this.tires = new Tire();
}
run() {
return this.engine.createEngine() + this.tires
}
}
/ / when used
const car = new Car();
Copy the code
To run, Engine and Tire instances are required, so create them directly in the Car class. At this point, the Car class is highly coupled to other classes.
Defect 1
In the future, if createEngine requires parameters to implement createEngine, you will need to change not only the Engine but also the Engine instance used in Car.
// Engine class
class Engine {
constructor(price){
this.price = price
}
createEngine(){
this.price... }}/ / class
class Car {
public engine: Engine;
public tires: Tires;
constructor() {
// The Car class code needs to be modified
this.engine = new Engine(price);
this.tires = new Tire();
}
run() {
return this.engine.createEngine() + this.tires
}
}
Copy the code
Defect 2
Suppose there are more kinds of tires in the future, such as class Tire1, class Tire2… All inherited from Tire.
// The tire class
class Tire {}class Tire1 extends Tire {}class Tire2 extends Tire {}class Car {
public engine: Engine;
public tires: Tires;
constructor() {
this.engine = new Engine();
// We need to modify the Car code
this.tires = new Tire1();
}
run() {
return this.engine.createEngine() + this.tires
}
}
Copy the code
Defect 3
Class Car is tricky to test because it depends on other classes, which may continue to depend on other classes. Classes are not decoupled, and the same test case may become unusable because of changes in dependent classes.
Using dependency Injection
// The tire class
class Tire {}// Engine class
class Engine {
createEngine(){}}/ / class
class Car {
// dependency injection
constructor(engine:Engine, tire:Tire) {}
run() {
return this.engine.createEngine() + this.tires
}
}
/ / when used
// Put this part in an IOC container to manage
const car = new Car(new Engine(), new Tire());
Copy the code
At this point, the above problem is solved, no matter how the dependent classes change, the class Car will not be broken. With dependency injection alone, you need to create an additional IOC container to manage dependencies. Some frameworks, such as the Spring framework (Java) on the server side, Nest framework (typescript) on the server side, and Anglar on the Web side, wrap the IOC container in a way that is convenient without our own wrapping.
conclusion
Back to the question, what does the decorator really bring to the front end? A proposal from Stage2 that has been around for a long time in the Anglar framework has been very unfriendly to beginners because of the obscurity of the decorator and the design patterns it creates. It’s common to hear in the back end that code should be abstract and modular to make it more robust and reduce code entropy, making it easier to test and extend. But when it comes to front-end modularity, the first thing that comes to mind is ESModule. Unfortunately, ESModule is not the true modularity that design patterns require, and decorators like Anglar and Nest demonstrate the promise of elegant modularity on the front end. I believe that with the widespread use of TS in the future, decorator patterns will also be widely used in projects.