ECMAScript This-Binding Syntax
This proposal introduces a new operator ::
which performs this
binding and method extraction.
It is a more detailed description of the bind operator strawman.
Examples
Using an iterator library implemented as a module of "virtual methods":
import { map, takeWhile, forEach } from "iterlib";
getPlayers()
::map(x => x.character())
::takeWhile(x => x.strength > 100)
::forEach(x => console.log(x));
Using a jQuery-like library of virtual methods:
// Create bindings for just the methods that we need
let { find, html } = jake;
// Find all the divs with class="myClass", then get all of the "p"s and
// replace their content.
document.querySelectorAll("div.myClass")::find("p")::html("hahaha");
Using method extraction to print the eventual value of a promise to the console:
Promise.resolve(123).then(::console.log);
Using method extraction to call an object method when a DOM event occurs:
$(".some-link").on("click", ::view.reset);
Motivation and Overview
With the introduction of arrow functions in ECMAScript 6, the need for explicitly binding closures to the lexical this
value has been dramatically reduced, resulting in a significant increase in language usability. However, there are still two use cases where explicit this
binding or injection is both common and awkward.
Calling a known function with a supplied this
argument:
let hasOwnProp = Object.prototype.hasOwnProperty;
let obj = { x: 100 };
hasOwnProp.call(obj, "x");
Extracting a method from an object:
Promise.resolve(123).then(console.log.bind(console));
This proposal introduces a new operator ::
which can be used as syntactic sugar for these use cases.
In its binary form, the ::
operator creates a bound function such that the left hand side of the operator is bound as the this
variable to the target function on the right hand side.
In its unary prefix form, the ::
operator creates a bound function such that the base of the supplied reference is bound as the this
variable to the target function.
If the bound function is immediately called, and the target function is strict mode, then the bound function itself is not observable and the bind operator can be optimized to an efficient direct function call.
NOTE: If the target function is not strict, then it may use function.callee
or arguments.callee
, which would result in an observable difference of behavior.
By providing syntactic sugar for these use cases we will enable a new class of "virtual method" library, which will have usability advantages over the standard adapter patterns in use today.
Prototypes
Syntax
LeftHandSideExpression[Yield] :
NewExpression[?Yield]
CallExpression[?Yield]
BindExpression[?Yield]
BindExpression[Yield] :
LeftHandSideExpression[?Yield] :: [lookahead ≠ new] MemberExpression[?Yield]
:: MemberExpression[?Yield]
CallExpression[Yield] :
MemberExpression[?Yield] Arguments[?Yield]
super Arguments[?Yield]
CallExpression[?Yield] Arguments[?Yield]
CallExpression[?Yield] [ Expression[In, ?Yield] ]
CallExpression[?Yield] . IdentifierName
CallExpression[?Yield] TemplateLiteral[?Yield]
BindExpression[?Yield] Arguments[?Yield]
Early Errors
BindExpression :
:: MemberExpression
It is a Syntax Error if the derived MemberExpression is not MemberExpression : MemberExpression . Identifier, MemberExpression : MemberExpression [ Expression ], or SuperProperty
It is a Syntax Error if the derived NewExpression is PrimaryExpression : CoverParenthesizedExpressionAndArrowParameterList and CoverParenthesizedExpressionAndArrowParameterList ultimately derives a phrase that, if used in place of NewExpression, would produce a Syntax Error according to these rules. This rule is recursively applied.
NOTE: The last rule means that expressions such as
::(((foo)))
produce early errors because of recursive application of the first rule.
Abstract Operation: InitializeBoundFunctionProperties ( F, target )
The abstract operation InitializeBoundFunctionProperties with arguments F and target is used to set the "length" and "name" properties of a bound function F. It performs the following steps:
- Let targetHasLength be HasOwnProperty(target,
"length"
). - If targetHasLength is
true
, then- Let targetLen be ? Get(target,
"length"
). - If Type(targetLen) is not
Number
, then let L be0
. - Else, let L be ToInteger(targetLen).
- Let targetLen be ? Get(target,
- Else let L be
0
. - Let status be ? DefinePropertyOrThrow(F,
"length"
, PropertyDescriptor {[[Value]]: L, [[Writable]]:false
, [[Enumerable]]:false
, [[Configurable]]:true
}). - Let targetName be ? Get(target,
"name"
). - If Type(targetName) is not
String
, then let targetName be the empty string. - Let status be ? SetFunctionName(F, targetName,
"bound"
). - Return F.
Runtime Semantics
BindExpression :
LeftHandSideExpression :: [lookahead ≠ new] MemberExpression
- Let baseReference be the result of evaluating LeftHandSideExpression.
- Let baseValue be GetValue(baseReference).
- Let targetReference be the result of evaluating MemberExpression.
- Let target be GetValue(targetReference).
- If IsCallable(target) is
false
, throw aTypeError
exception. - Let F be ? BoundFunctionCreate(target, baseValue, «»).
- Return InitializeBoundFunctionProperties(F, target).
BindExpression :
:: MemberExpression
- Let targetReference be the result of evaluating MemberExpression.
- Assert: IsPropertyReference(targetReference) is
true
. - Let thisValue be GetThisValue(targetReference).
- Let target be GetValue(targetReference).
- If IsCallable(target) is
false
, throw aTypeError
exception. - Let F be ? BoundFunctionCreate(target, thisValue, «»).
- Return ? InitializeBoundFunctionProperties(F, target).
Future Extensions: Bound Constructors
This syntax can be extended by introducing bound constructors. When the binary ::
operator is followed by the new
keyword, the constructor on the left hand side is wrapped by a callable function.
class User {
constructor(name) {
this.name = name;
}
}
let users = ["userA", "userB"].map(User::new);
console.log(users);
/*
[ (User) { name: "userA" },
(User) { name: "userB" }]
*/
Runtime Semantics
BindExpression:
LeftHandSideExpression :: new
- Let targetReference be the result of evaluating LeftHandSideExpression.
- Let target be GetValue(targetReference).
- If IsConstructor(target) is
false
, throw aTypeError
exception. - Let F be a new built-in function as defined in Bound Constructor Wrapper Functions.
- Set the [[BoundTargetConstructor]] internal slot of F to target.
- Return ? InitializeBoundFunctionProperties(F, target).
Bound Constructor Wrapper Functions
A bound constructor wrapper function is an anonymous built-in function that has a [[BoundTargetConstructor]] internal slot.
When a bound constructor wrapper function F is called with zero or more args, it performs the following steps:
- Assert: F has a [[BoundTargetConstructor]] internal slot.
- Let target be the value of F's [[BoundTargetConstructor]] internal slot.
- Assert: IsConstructor(target).
- Let args be a List consisting of all the arguments passed to this function.
- Return ? Construct(target, args).