Toc
  1. TypeScript装饰器+源数据编程:从新手到专家Part1
    1. brief
    2. The difference between Annotations and Decorators
    3. Decorators in TypeScript
    4. Method decorators
    5. Conclusions
Toc
0 results found
catzillaorz
注解&装饰器Decorators & metadata reflection in TypeScript: From Novice to Expert (Part I)
2021/08/17 TS
TypeScript装饰器+源数据编程:从新手到专家Part1
  • 原文链接
  • 本文只从阅读原文中提取部分内容,方便临时查阅
  • MAY 18, 2015为原文发布日期,部分信息可能已经不再适用,但是基本原理依然没变,需要仔细研读。

A few months ago Microsoft and Google announced that they were working together on TypeScript and Angular 2.0
We’re excited to announce that we have converged the TypeScript and AtScript languages, and that Angular 2, the next version of the popular JavaScript library for building web sites and web apps, will be developed with TypeScript.

20210824151621-2021-08-24-15-16-22-

brief
  • This series will cover:

  • PART I: Method decorators

  • PART II: Property decorators & Class decorators

  • PART III: Parameter decorators & Decorator factory

  • PART IV: Types serialization & The metadata reflection API

The difference between Annotations and Decorators
  • Annotation and decorator are pretty much the same:

Annotations and decorators are nearly the same thing.
From a consumer perspective we have exactly the same syntax.
The only thing that differs is that we don’t have control over how annotations are added as meta data to our code.
Whereas decorators is rather an interface to build something that ends up as annotation.

Over a long term, however, we can just focus on decorators, since those are a real proposed standard. AtScript is TypeScript and TypeScript implements decorators.

Let’s take a look to the TypeScript’s decorators syntax.

Note: If you want to learn more about the difference between Annotations and Decorators there is a great article by Pascal Precht on this topic.

Decorators in TypeScript

In the TypeScript source code we can find the signature of the available types of decorators:

declare type ClassDecorator = extends Function>(target: TFunction) => TFunction | void;

declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;

declare type MethodDecorator = (target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor) => TypedPropertyDescriptor | void;

declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void;
Method decorators
  • To invoke a method decorator we need to prefix the method that we wish to decorate with the @ character follow by the name of the decorator. In the case of a decorator named log, the syntax will look as follows:
class C {
@log
foo(n: number) {
return n * 2;
}
}
  • Before we can actually use @log we need to declare the method decorator somewhere in our application. Let’s take a look to the log method decorator implementation.
function log(target: Function, key: string, value: any) {
return {
value: function (...args: any[]) {
var a = args.map(a => JSON.stringify(a)).join();
var result = value.value.apply(this, args);
var r = JSON.stringify(result);
console.log(`Call: ${key}(${a}) => ${r}`);
return result;
}
};
}
  • Note: Please take a look to updates at the end of this post for an alternative implementation, which a avoids one potential issue.

  • A method decorators takes a 3 arguments:

  • target the method being decorated.

  • key the name of the method being decorated.

  • value a property descriptor of the given property if it exists on the object, undefined otherwise. - The property descriptor is obtained by invoking the Object.getOwnPropertyDescriptor() function.

  • There is something strange right? We didn’t pass any of these parameters when we used the decorator @log in the C class definition. At this point we should be wondering who is providing those arguments? and Where is the log method being invoked?

  • We can find the answers to these questions by examining the code that the TypeScript compiler will generate for the code above.

var C = (function () {
function C() {
}
C.prototype.foo = function (n) {
return n * 2;
};
Object.defineProperty(C.prototype, "foo",
__decorate([
log
], C.prototype, "foo", Object.getOwnPropertyDescriptor(C.prototype, "foo")));
return C;
})();
  • Without the @log decorator the generated JavaScript for the C class would just be as follows.
var C = (function () {
function C() {
}
C.prototype.foo = function (n) {
return n * 2;
};
return C;
})();
  • But when we add the @log decorator the following additional code is added to the class definition by the TypeScript compiler.
Object.defineProperty(
__decorate(
[log], // decorators
C.prototype, // target
"foo", // key
Object.getOwnPropertyDescriptor(C.prototype, "foo") // desc
);
);

If we read the MDN documentation we will learn that the following about the defineProperty function.

The Object.defineProperty() method defines a new property directly on an object,
or modifies an existing property on an object, and returns the object.

  • The TypeScript compiler is passing the prototype of C, the name of the method being decorated (foo) and the return of a function named __decorate to the defineProperty method.

  • The TypeScript compiler is using the defineProperty method to override the method being decorated. The new method implementation will be the value returned by the function __decorate. By now we should have a new question: Where is the __decorate function declared?

var __extends = this.__extends || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
__.prototype = b.prototype;
d.prototype = new __();
};
  • In a similar manner, when we use a decorator a function named __decorator is generated by the TypeScript compiler. Let’s take a look to the __decorator funcion.
var __decorate = this.__decorate || function (decorators, target, key, desc) {
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
return Reflect.decorate(decorators, target, key, desc);
}
switch (arguments.length) {
case 2:
return decorators.reduceRight(function(o, d) {
return (d && d(o)) || o;
}, target);
case 3:
return decorators.reduceRight(function(o, d) {
return (d && d(target, key)), void 0;
}, void 0);
case 4:
return decorators.reduceRight(function(o, d) {
return (d && d(target, key, o)) || o;
}, desc);
}
};
  • The first line in the code snippet above is using an OR operator to ensure that if the function __decorator is generated more than once it will not be override again and again. In the second line, we can observe a conditional statement:
if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
  • The conditional statement is used to detect an upcoming JavaScript feature: The metadata reflection API.

  • Note: We will focus on the metadata reflection API towards the end of this post series so let’s ignore it for now.

  • Let’s remember how did we get here for a second. The method foo is about to be override by the return of the function __decorate which was invoked with the following parameters.

__decorate(
[log], // decorators
C.prototype, // target
"foo", // key
Object.getOwnPropertyDescriptor(C.prototype, "foo") // desc
);

We are now inside the __decorate method and because the metadata reflection API is not available, a fallback is about to be executed.

// arguments.length === number fo arguments passed to __decorate()
switch (arguments.length) {
case 2:
return decorators.reduceRight(function(o, d) {
return (d && d(o)) || o;
}, target);
case 3:
return decorators.reduceRight(function(o, d) {
return (d && d(target, key)), void 0;
}, void 0);
case 4:
return decorators.reduceRight(function(o, d) {
return (d && d(target, key, o)) || o;
}, desc);
}

Because 4 parameters are passed to the __decorate method, the case 4 will be executed. Understanding this piece of code can be a challenge because the name of the variables are not really descriptive but we are not scared of it right?

Let’s start by learning about the reduceRight method.

The reduceRight method applies a function against an accumulator and each value of the array (from right-to-left) has to reduce it to a single value.

  • The code below performs the exact same operation but it had been rewritten to facilitate its understanding
[log].reduceRight(function(log, desc) {
if(log) {
return log(C.prototype, "foo", desc);
}
else {
return desc;
}
}, Object.getOwnPropertyDescriptor(C.prototype, "foo"));

When the code above is executed the decorator log is invoked and we can see that some parameters are passed to it: C.prototype,”foo“ and previousValue. So we have finally answered our original questions:

  • Who is providing those arguments?
  • Where is the log method being invoked?

If we return to the log decorator implementation we will be able to understand much better what happens when it is invoked.

function log(target: Function, key: string, value: any) {

// target === C.prototype
// key === "foo"
// value === Object.getOwnPropertyDescriptor(C.prototype, "foo")

return {
value: function (...args: any[]) {

// convert list of foo arguments to string
var a = args.map(a => JSON.stringify(a)).join();

// invoke foo() and get its return value
var result = value.value.apply(this, args);

// convert result to string
var r = JSON.stringify(result);

// display in console the function call details
console.log(`Call: ${key}(${a}) => ${r}`);

// return the result of invoking foo
return result;
}
};
}
  • After decorating the foo method it will continue to work as usually but it will also execute the extra logging functionality added by the log the decorator.
var c = new C();
var r = c.foo(23); // "Call: foo(23) => 46"
console.log(r); // 46
Conclusions

It has been a journey right? I hope you have enjoyed as much as I have. We are just getting started but we already know enough to create some truly awesome stuff.

Method decorators can be used for many interesting features. For example, If you have ever worked with spies in testing frameworks like SinonJS you will probably get excited when you realize that decorators are going to allow us to do things like create spies by just adding a @spy decorator.

In the next chapter of this series we will learn how to work with Property decorators. Don’t forget to subscribe if you don’t want to miss it out!

总结:装饰器从某种意义上讲就是一种“注解”机制的实现,对于angular中的“注解”而言,他是一种被指定了特殊编译过程的装饰器。明白了装饰器的原理,也就能够理解注解对于angular框架来说的意义。再说TS中装饰器,如果把其他框架结合TS使用 ,那么TS中的注解,从某种意义上来说,就是如同angular注解一样,是一种注解AOP编程思维的转换。

打赏
支付宝
微信
本文作者:catzillaorz
版权声明:本文首发于catzillaorz的博客,转载请注明出处!