GithubHelp home page GithubHelp logo

javascript-decorators's Introduction

This file is under active development. Refer to interop/reusability.md for the most up to date description.

Summary

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.

Detailed Design

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:

class Person {
  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:

class Person {
  @readonly
  name() { return `${this.first} ${this.last}` }
}

Now, before installing the descriptor onto Person.prototype, the engine first invokes the decorator:

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 });
}

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:

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;
}

A more detailed example illustrating a simple decorator that memoizes an accessor.

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;
}

It is also possible to decorate the class itself. In this case, the decorator takes the target constructor.

// A simple decorator
@annotation
class MyClass { }

function annotation(target) {
   // Add a property on target
   target.annotated = true;
}

Since decorators are expressions, decorators can take additional arguments and act like a factory.

@isTestable(true)
class MyClass { }

function isTestable(value) {
   return function decorator(target) {
      target.isTestable = value;
   }
}

The same technique could be used on property decorators:

class C {
  @enumerable(false)
  method() { }
}

function enumerable(value) {
  return function (target, key, descriptor) {
     descriptor.enumerable = value;
     return descriptor;
  }
}

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.

Desugaring

Class Declaration

Syntax

@F("color")
@G
class Foo {
}

Desugaring (ES6)

var Foo = (function () {
  class Foo {
  }

  Foo = F("color")(Foo = G(Foo) || Foo) || Foo;
  return Foo;
})();

Desugaring (ES5)

var Foo = (function () {
  function Foo() {
  }

  Foo = F("color")(Foo = G(Foo) || Foo) || Foo;
  return Foo;
})();

Class Method Declaration

Syntax

class Foo {
  @F("color")
  @G
  bar() { }
}

Desugaring (ES6)

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;
})();

Desugaring (ES5)

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 Accessor Declaration

Syntax

class Foo {
  @F("color")
  @G
  get bar() { }
  set bar(value) { }
}

Desugaring (ES6)

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;
})();

Desugaring (ES5)

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;
})();

Object Literal Method Declaration

Syntax

var o = {
  @F("color")
  @G
  bar() { }
}

Desugaring (ES6)

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;
})();

Desugaring (ES5)

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;
})();

Object Literal Accessor Declaration

Syntax

var o = {
  @F("color")
  @G
  get bar() { }
  set bar(value) { }
}

Desugaring (ES6)

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;
})();

Desugaring (ES5)

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;
})();

Grammar

  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]

Notes

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.

javascript-decorators's People

Contributors

jayphelps avatar jeffmo avatar nesk avatar stefanpenner avatar wycats avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.