We are pleased to announce that we are combining TypeScript and AtScript languages, and also that Angular 2, the next version of the popular JavaScript library for creating websites and web applications, will be developed in TypeScript
Annotations are a way to add metadata to a class declaration for use in dependency injection or compiler directives.
Annotations and decorators are about the same. From the user's point of view, they have exactly the same syntax. The difference is that we do not control how annotations add metadata to our code. We can view decorators as an interface for building something that behaves like annotations.
In the long term, however, we will focus on decorators, since they are the existing proposed standard. AtScript is TypeScript, and TypeScript implements decorators.
declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void; declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void; declare type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void; declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void;
class C { @log foo(n: number) { return n * 2; } }
@log
, you must declare the decorator itself somewhere in our application. Let's look at its 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; } }; }
@log
decorator in the class C
declaration. In this regard, two questions arise: who is passing these arguments? and where exactly is the log method called? 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; })();
@log
decorator @log
generated Javascript code for class C
would look like this: var C = (function () { function C() { } C.prototype.foo = function (n) { return n * 2; }; return C; })();
@log
following code to the class definition with the TypeScript compiler: Object.defineProperty(C.prototype, "foo", __decorate( [log], // decorators C.prototype, // target "foo", // key Object.getOwnPropertyDescriptor(C.prototype, "foo") // desc ); );
TheObject.defineProperty()
method detects a new or changes an existing property directly on an object and returns this object.
C
, the name of the method to be decorated ('foo'), and the result of the __decorate
function to the __decorate
method.defineProperty
used to override the method being decorated. The new implementation of the method is the result of the function __decorate
. A new question arises: where is the __decorate
function __decorate
?extends
, a function called __extends
generated by the compiler. 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 __(); };
__decorate
generated by the TypeScript compiler. Let's take a look at it. 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); } };
OR
operator to make sure that the __decorate
function generated more than once will not be overwritten again and again. In the second line we can notice the condition: if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
foo
method is overridden by the result of the __decorate
function, which is called with the following parameters: __decorate( [log], // decorators C.prototype, // target "foo", // key Object.getOwnPropertyDescriptor(C.prototype, "foo") // desc );
__decorate
function and, since the metadata reflection API is not available, the version generated by the compiler will be executed // arguments.length === , __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); }
__decorate
method, the last option will be selected. Dealing with this code is not so easy because of the meaningless names of variables, but we are not afraid, are we?reduceRight
applies the accumulating function to each element of the array (in order from right to left) and returns a single value.
[log].reduceRight(function(log, desc) { if(log) { return log(C.prototype, "foo", desc); } else { return desc; } }, Object.getOwnPropertyDescriptor(C.prototype, "foo"));
log
decorator is called and we see the parameters passed to it: C.prototype
, "foo"
and previousValue
. That is, now we know the answers to our questions:log
function called?log
decorator, we can understand much better what happens when we call it. // target === C.prototype // key === "foo" // value === Object.getOwnPropertyDescriptor(C.prototype, "foo") return { value: function (...args: any[]) { // , foo, var a = args.map(a => JSON.stringify(a)).join(); // foo() var result = value.value.apply(this, args); // var r = JSON.stringify(result); // console.log(`Call: ${key}(${a}) => ${r}`); // foo return result; } };
foo
method will continue to work as usual, but its calls will also run additional logging functionality added in the log
decorator. var c = new C(); var r = c.foo(23); // "Call: foo(23) => 46" console.log(r); // 46
"" (spy
) in test frameworks like SinonJS , you might be delighted to use decorators to create "spies" simply by adding the @spy
decorator.Source: https://habr.com/ru/post/275003/
All Articles