当前文件处在活跃的开发状态。最新的描述请查看
interop/reusability.md
。
装饰器使我们在设计类和属性时注释和改变它们成为可能。
尽管ES5 对象字面量字段的值支持任意的表达式,但 ES6 的类只支持作为值得函数字面量。装饰器使 ES6 重新拥有了在设计时运行代码的能力,同时保持声明式语法。
一个装饰器是:
- 一个表达式
- 对函数进行求值运算
- 接受目标、名称和装饰器描述作为参数
- 返回准备安装到目标对象上的装饰器的描述[可选]
考虑定义一个简单的类:
class Person {
name() { return `${this.first} ${this.last}` }
}
对这个类进行求值会把 name
函数安装到 Person.prototype
上,大致如下:
Object.defineProperty(Person.prototype, 'name', {
value: specifiedFunction,
enumerable: false,
configurable: true,
writable: true
});
装饰器放在属性定义之前:
class Person {
@readonly
name() { return `${this.first} ${this.last}` }
}
现在,在安装描述符到 Person.prototype
之前,会调用装饰器:
let description = {
type: 'method',
initializer: () => specifiedFunction,
enumerable: false,
configurable: true,
writable: true
};
description = readonly(Person.prototype, 'name', description) || description;
defineDecoratedProperty(Person.prototype, 'name', description);
function defineDecoratedProperty(target, { initializer, enumerable, configurable, writable }) {
Object.defineProperty(target, { value: initializer(), enumerable, configurable, writable });
}
现在在相应的 defineProperty
真正调用之前,我们有机会改变类的属性。
装饰器放在 getters 和/或 setters 之前影响属性存取操作:
class Person {
@nonenumerable
get kidCount() { return this.children.length; }
}
let description = {
type: 'accessor',
get: specifiedGetter,
enumerable: true,
configurable: true
}
function nonenumerable(target, name, description) {
descriptor.enumerable = false;
return descriptor;
}
一个更详细的例子演示一个记住存取器的简单装饰器:
class Person {
@memoize
get name() { return `${this.first} ${this.last}` }
set name(val) {
let [first, last] = val.split(' ');
this.first = first;
this.last = last;
}
}
let memoized = new WeakMap();
function memoize(target, name, descriptor) {
let getter = descriptor.get, setter = descriptor.set;
descriptor.get = function() {
let table = memoizationFor(this);
if (name in table) { return table[name]; }
return table[name] = getter.call(this);
}
descriptor.set = function(val) {
let table = memoizationFor(this);
setter.call(this, val);
table[name] = val;
}
}
function memoizationFor(obj) {
let table = memoized.get(obj);
if (!table) { table = Object.create(null); memoized.set(obj, table); }
return table;
}
装饰类也是可以的。这种情况下,装饰器接受目标构造器作为参数。
// A simple decorator
@annotation
class MyClass { }
function annotation(target) {
// Add a property on target
target.annotated = true;
}
因为装饰器是表达式,所以装饰器可以像工厂函数一样使用接受额外的参数。
@isTestable(true)
class MyClass { }
function isTestable(value) {
return function decorator(target) {
target.isTestable = value;
}
}
属性装饰器也可以这样用:
class C {
@enumerable(false)
method() { }
}
function enumerable(value) {
return function (target, key, descriptor) {
descriptor.enumerable = value;
return descriptor;
}
}
因为描述符装饰器作用于目标,它们自然也可以应用在静态方法上。唯一的区别就是传入装饰器的第一个参数将会是类本身(构造器)而不是原型,因为原型是原始的 Object.defineProperty
的目标。
基于同样的原因,描述符装饰器也可以用于对象字面量,只要把需要被创建的对象传递给装饰器。
@F("color")
@G
class Foo {
}
var Foo = (function () {
class Foo {
}
Foo = F("color")(Foo = G(Foo) || Foo) || Foo;
return Foo;
})();
var Foo = (function () {
function Foo() {
}
Foo = F("color")(Foo = G(Foo) || Foo) || Foo;
return Foo;
})();
class Foo {
@F("color")
@G
bar() { }
}
var Foo = (function () {
class Foo {
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);
return Foo;
})();
var Foo = (function () {
function Foo() {
}
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);
return Foo;
})();
class Foo {
@F("color")
@G
get bar() { }
set bar(value) { }
}
var Foo = (function () {
class Foo {
get bar() { }
set bar(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);
return Foo;
})();
var Foo = (function () {
function Foo() {
}
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);
return Foo;
})();
var o = {
@F("color")
@G
bar() { }
}
var o = (function () {
var _obj = {
bar() { }
}
var _temp;
_temp = F("color")(_obj, "bar",
_temp = G(_obj, "bar",
_temp = void 0) || _temp) || _temp;
if (_temp) Object.defineProperty(_obj, "bar", _temp);
return _obj;
})();
var o = (function () {
var _obj = {
bar: function () { }
}
var _temp;
_temp = F("color")(_obj, "bar",
_temp = G(_obj, "bar",
_temp = void 0) || _temp) || _temp;
if (_temp) Object.defineProperty(_obj, "bar", _temp);
return _obj;
})();
var o = {
@F("color")
@G
get bar() { }
set bar(value) { }
}
var o = (function () {
var _obj = {
get bar() { }
set bar(value) { }
}
var _temp;
_temp = F("color")(_obj, "bar",
_temp = G(_obj, "bar",
_temp = void 0) || _temp) || _temp;
if (_temp) Object.defineProperty(_obj, "bar", _temp);
return _obj;
})();
var o = (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 = void 0) || _temp) || _temp;
if (_temp) Object.defineProperty(_obj, "bar", _temp);
return _obj;
})();
DecoratorList [Yield] :
DecoratorList [?Yield]opt Decorator [?Yield]
Decorator [Yield] :
@
LeftHandSideExpression [?Yield]
PropertyDefinition [Yield] :
IdentifierReference [?Yield]
CoverInitializedName [?Yield]
PropertyName [?Yield] :
AssignmentExpression [In, ?Yield]
DecoratorList [?Yield]opt MethodDefinition [?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]
NewSuper Arguments [?Yield]
new
MemberExpression [?Yield] Arguments [?Yield]
SuperProperty [Yield] :
super
CoverMemberExpressionSquareBracketsAndComputedPropertyName [?Yield]
super
.
IdentifierName
ClassDeclaration [Yield, Default] :
DecoratorList [?Yield]opt class
BindingIdentifier [?Yield] ClassTail [?Yield]
[+Default] DecoratorList [?Yield]opt class
ClassTail [?Yield]
ClassExpression [Yield, GeneratorParameter] :
DecoratorList [?Yield]opt class
BindingIdentifier [?Yield]opt ClassTail [?Yield, ?GeneratorParameter]
ClassElement [Yield] :
DecoratorList [?Yield]opt MethodDefinition [?Yield]
DecoratorList [?Yield]opt static
MethodDefinition [?Yield]
为了更直接的支持只有元数据的装饰器,满足未来可能需要的静态分析,TypeScript 项目让它的使用者可以定义环境装饰器,它支持一种严格的语法,可以不进行求值就能被正确的分析。