This file is under active development. Refer to
interop/reusability.mdfor the most up to date description.
Decorators make it possible to annotate and modify classes and properties at design time.
While ES5 object literals support arbitrary expressions in the value position, ES6 classes only support literal functions as values. Decorators restore the ability to run code at design time, while maintaining a declarative syntax.
A decorator is:
- an expression
- that evaluates to a function
- that takes the target, name, and decorator descriptor as arguments
- and optionally returns a decorator descriptor to install on the target object
Consider a simple class definition:
classPerson{name(){return`${this.first}${this.last}`}}Evaluating this class results in installing the name function onto
Person.prototype, roughly like this:
Object.defineProperty(Person.prototype,'name',{value: specifiedFunction,enumerable: false,configurable: true,writable: true});A decorator precedes the syntax that defines a property:
classPerson{
@readonlyname(){return`${this.first}${this.last}`}}Now, before installing the descriptor onto Person.prototype, the engine first
invokes the decorator:
letdescription={type: 'method',initializer: ()=>specifiedFunction,enumerable: false,configurable: true,writable: true};description=readonly(Person.prototype,'name',description)||description;defineDecoratedProperty(Person.prototype,'name',description);functiondefineDecoratedProperty(target,{ initializer, enumerable, configurable, writable }){Object.defineProperty(target,{value: initializer(), enumerable, configurable, writable });}The has an opportunity to intercede before the relevant defineProperty actually occurs.
A decorator that precedes syntactic getters and/or setters operates on an accessor description:
classPerson{
@nonenumerablegetkidCount(){returnthis.children.length;}}letdescription={type: 'accessor',get: specifiedGetter,enumerable: true,configurable: true}functionnonenumerable(target,name,description){descriptor.enumerable=false;returndescriptor;}A more detailed example illustrating a simple decorator that memoizes an accessor.
classPerson{
@memoizegetname(){return`${this.first}${this.last}`}setname(val){let[first,last]=val.split(' ');this.first=first;this.last=last;}}letmemoized=newWeakMap();functionmemoize(target,name,descriptor){letgetter=descriptor.get,setter=descriptor.set;descriptor.get=function(){lettable=memoizationFor(this);if(nameintable){returntable[name];}returntable[name]=getter.call(this);}descriptor.set=function(val){lettable=memoizationFor(this);setter.call(this,val);table[name]=val;}}functionmemoizationFor(obj){lettable=memoized.get(obj);if(!table){table=Object.create(null);memoized.set(obj,table);}returntable;}It is also possible to decorate the class itself. In this case, the decorator takes the target constructor.
// A simple decorator
@annotationclassMyClass{}functionannotation(target){// Add a property on targettarget.annotated=true;}Since decorators are expressions, decorators can take additional arguments and act like a factory.
@isTestable(true)classMyClass{}functionisTestable(value){returnfunctiondecorator(target){target.isTestable=value;}}The same technique could be used on property decorators:
classC{
@enumerable(false)method(){}}functionenumerable(value){returnfunction(target,key,descriptor){descriptor.enumerable=value;returndescriptor;}}Because descriptor decorators operate on targets, they also naturally work on
static methods. The only difference is that the first argument to the decorator
will be the class itself (the constructor) rather than the prototype, because
that is the target of the original Object.defineProperty.
For the same reason, descriptor decorators work on object literals, and pass the object being created to the decorator.
@F("color")
@GclassFoo{}varFoo=(function(){classFoo{}Foo=F("color")(Foo=G(Foo)||Foo)||Foo;returnFoo;})();varFoo=(function(){functionFoo(){}Foo=F("color")(Foo=G(Foo)||Foo)||Foo;returnFoo;})();classFoo{
@F("color")
@Gbar(){}}varFoo=(function(){classFoo{bar(){}}var_temp;_temp=F("color")(Foo.prototype,"bar",_temp=G(Foo.prototype,"bar",_temp=Object.getOwnPropertyDescriptor(Foo.prototype,"bar"))||_temp)||_temp;if(_temp)Object.defineProperty(Foo.prototype,"bar",_temp);returnFoo;})();varFoo=(function(){functionFoo(){}Foo.prototype.bar=function(){}var_temp;_temp=F("color")(Foo.prototype,"bar",_temp=G(Foo.prototype,"bar",_temp=Object.getOwnPropertyDescriptor(Foo.prototype,"bar"))||_temp)||_temp;if(_temp)Object.defineProperty(Foo.prototype,"bar",_temp);returnFoo;})();classFoo{
@F("color")
@Ggetbar(){}setbar(value){}}varFoo=(function(){classFoo{getbar(){}setbar(value){}}var_temp;_temp=F("color")(Foo.prototype,"bar",_temp=G(Foo.prototype,"bar",_temp=Object.getOwnPropertyDescriptor(Foo.prototype,"bar"))||_temp)||_temp;if(_temp)Object.defineProperty(Foo.prototype,"bar",_temp);returnFoo;})();varFoo=(function(){functionFoo(){}Object.defineProperty(Foo.prototype,"bar",{get: function(){},set: function(value){},enumerable: true,configurable: true});var_temp;_temp=F("color")(Foo.prototype,"bar",_temp=G(Foo.prototype,"bar",_temp=Object.getOwnPropertyDescriptor(Foo.prototype,"bar"))||_temp)||_temp;if(_temp)Object.defineProperty(Foo.prototype,"bar",_temp);returnFoo;})();varo={
@F("color")
@Gbar(){}}varo=(function(){var_obj={bar(){}}var_temp;_temp=F("color")(_obj,"bar",_temp=G(_obj,"bar",_temp=void0)||_temp)||_temp;if(_temp)Object.defineProperty(_obj,"bar",_temp);return_obj;})();varo=(function(){var_obj={bar: function(){}}var_temp;_temp=F("color")(_obj,"bar",_temp=G(_obj,"bar",_temp=void0)||_temp)||_temp;if(_temp)Object.defineProperty(_obj,"bar",_temp);return_obj;})();varo={
@F("color")
@Ggetbar(){}setbar(value){}}varo=(function(){var_obj={getbar(){}setbar(value){}}var_temp;_temp=F("color")(_obj,"bar",_temp=G(_obj,"bar",_temp=void0)||_temp)||_temp;if(_temp)Object.defineProperty(_obj,"bar",_temp);return_obj;})();varo=(function(){var_obj={}Object.defineProperty(_obj,"bar",{get: function(){},set: function(value){},enumerable: true,configurable: true});var_temp;_temp=F("color")(_obj,"bar",_temp=G(_obj,"bar",_temp=void0)||_temp)||_temp;if(_temp)Object.defineProperty(_obj,"bar",_temp);return_obj;})();DecoratorList [Yield] :
DecoratorList [?Yield]optDecorator [?Yield]
Decorator [Yield] :@LeftHandSideExpression [?Yield]
PropertyDefinition [Yield] :
IdentifierReference [?Yield]
CoverInitializedName [?Yield]
PropertyName [?Yield]:AssignmentExpression [In, ?Yield]
DecoratorList [?Yield]optMethodDefinition [?Yield]
CoverMemberExpressionSquareBracketsAndComputedPropertyName [Yield] :[Expression [In, ?Yield]]
NOTE The production CoverMemberExpressionSquareBracketsAndComputedPropertyName is used to cover parsing a MemberExpression that is part of a Decorator inside of an ObjectLiteral or ClassBody, to avoid lookahead when parsing a decorator against a ComputedPropertyName.
PropertyName [Yield, GeneratorParameter] :
LiteralPropertyName
[+GeneratorParameter] CoverMemberExpressionSquareBracketsAndComputedPropertyName
[~GeneratorParameter] CoverMemberExpressionSquareBracketsAndComputedPropertyName [?Yield]
MemberExpression [Yield] :
[Lexical goal InputElementRegExp] PrimaryExpression [?Yield]
MemberExpression [?Yield]CoverMemberExpressionSquareBracketsAndComputedPropertyName [?Yield]
MemberExpression [?Yield].IdentifierName
MemberExpression [?Yield]TemplateLiteral [?Yield]
SuperProperty [?Yield]
NewSuperArguments [?Yield]newMemberExpression [?Yield]Arguments [?Yield]
SuperProperty [Yield] :superCoverMemberExpressionSquareBracketsAndComputedPropertyName [?Yield]super.IdentifierName
ClassDeclaration [Yield, Default] :
DecoratorList [?Yield]optclassBindingIdentifier [?Yield]ClassTail [?Yield]
[+Default] DecoratorList [?Yield]optclassClassTail [?Yield]
ClassExpression [Yield, GeneratorParameter] :
DecoratorList [?Yield]optclassBindingIdentifier [?Yield]optClassTail [?Yield, ?GeneratorParameter]
ClassElement [Yield] :
DecoratorList [?Yield]optMethodDefinition [?Yield]
DecoratorList [?Yield]optstaticMethodDefinition [?Yield]
In order to more directly support metadata-only decorators, a desired feature for static analysis, the TypeScript project has made it possible for its users to define ambient decorators that support a restricted syntax that can be properly analyzed without evaluation.