As we know, ECMA-262-5 standardized bind
method of the Function.prototype
. The source of the current version of this method is “Prototype.js” library, although, the standardized version has several differences. This approach is well known and was successfully used in ES3, so the main purpose of the current note is to show the technical details and differences (which can cause confusing) of the ES5 implementation.
Purposes
The current implementation of the Function.prototype.bind has two purposes, which are the static bound this
value and the partial application of a function. Let’s consider them.
Bound this value
The main purpose of the bind
method is to statically bind a this
value for subsequent calls of a function. As we considered in the ECMA-262-3 in detail. Chapter 3. This, a this
value can vary in every function call. So, the main purpose of the bind
is to fix this “issue” which can appear e.g. when we attach a method of an object as event handler of some DOM element. Using a bound function we can always have a correct this value in the event’s handling.
var widget = { state: {...}, onClick: function onWidgetClick(event) { if (this.state.active) { ... } } }; document.getElementById("widget").onclick = widget.onClick.bind(widget);
I used simple click event attaching in the example above — via onclick
method, but on practice you can better use multiple listeners pattern using addEventListener
or attachEvent
. However the goal of the example is to show how this
value is predefined and bound to the widget
object inside the onclick
method, and this.state
property is available.
Partial application
Another purpose of the current “bind” implementation is a currying (or closer to mathematics — a partial application of a function). It is the technique of transforming a function of multiple arguments to a chain of functions each with a single argument that produce at the end the same result. Which means we can generate functions based on other functions, and some (or maybe all) arguments of the new function can be predefined. And applying the new function with the rest arguments (with the last part of arguments) forms a partial application of the function.
function foo(x, y) { // partial if (typeof y == "undefined") { return function partialFoo(y) { // complete return x + y; }; } // complete return x + y; } foo(10, 20); // 30 foo(10)(20); // 30 var partialFoo = foo(10); // function partialFoo(20); // 30
The practical rationale of partial application can be the case when you often use a function with the same (repeating from call to call) part of arguments. In such case it is convenient to encapsulate these arguments, making a new partial function. Another good practical example can be again attaching an event listener with bound this
value and some bound data which will be available at the activation of the listener’s handler. As a simplest example of such bound data can be event object itself. Theoretically, partial application is related with some mathematical theorems and also with lambda calculus where functions have only one argument.
So, practically, using “bind” method, we can make partial functions:
function foo(x, y) { return x + y; } var partialFoo = foo.bind(null, 10); partialFoo(20) // 30
In this example a static bound this value is not essential for us (we didn’t even use this
keyword inside the function), but the currying is. Of course, we can combine these two purposes of the bind
method.
Implementation features
In the previous section a brief overview of the bind
method and its purpose was described. However, you should already know about it and this note is mostly about technical details of the bind
method and bound functions exactly in ECMA-262-5.
Recently, there were debates whether the semantic implementation of this method should be the same as in “Prototype.js” library, however it is so now. Although, an alternative form with accepting an array of partial arguments was mentioned several times, arguing that it would be more convenient.
In contrast with the similar bind
implementations written on JavaScript (before the ES5), the built-in implementation produces specific version of a normal function. The bound functions (see 15.3.4.5) do not have some internal properties which normal functions do. This is made for the optimization. Thus, a bound function does not have a prototype
property or the [[Code]]
(the function body code), [[FormalParameters]]
(the list of formal parameter names), and [[Scope]]
(a parent lexical or variable environment) internal properties.
Thus, absence of the [[Scope]]
property provides optimization, because such incomplete function objects do not form closures. They are just intermediate proxies which delegate to the original functions.
However, a bound function has additionally the following internal properties: [[TargetFunction]]
— a reference to a target function from which the bound function is created; [[BoundThis]]
— exactly a bound this
value; [[BoundArgs]]
— a bound arguments which are used in partial application of the function.
Three important internal methods — [[Call]]
, [[Construct]]
and [[HasInstance]]
are overloaded for the bound functions.
The [[Call]]
internal method of every function is activated when we call a function (via the call expression, e.g. foo()
or with using call
and apply
methods). When the [[Call]]
internal method of a bound function is activated, it after own handling delegates to the original [[Call]]
of the target function, passing needed this
value and arguments. With pseudo-code it can be presented so:
boundFn.[[Call]] = function (thisValue, extraArgs): var boundArgs = boundFn.[[BoundArgs]], boundThis = boundFn.[[BoundThis]], targetFn = boundFn.[[TargetFunction]], args = boundArgs.concat(extraArgs); return targetFn.[[Call]](boundThis, args);
And then a normal [[Call]]
of the target function is activated, establishing a new execution context evaluating the [[Code]]
property of the target function (see 13.2.1 — a normal [[Call]]
activation).
function F(x, y) { this.x = x; this.y = y; return this; } // bind "this" value and partially "x" argument var BoundF = F.bind({z: 30}, 10); // create object passing partial "y" argument var objectFromCall = BoundF(20); // because "this" inside "BoundF" refers // to {z: 30} object, then we have alert([ objectFromCall.x, // 10, from [[BoundArgs]] objectFromCall.y, // 20, from extraArgs objectFromCall.z, // 30, from [[BoundThis]] ]);
In the algorithm of the bound [[Call]]
we also see that passed by the caller this
value (first argument thisValue
in the pseudo-code) does not participate in actions, because the [[BoundThis]]
is always used as this
value and exactly it’s passed to the original [[Call]]
. Therefore, using call
or apply
methods in respect of providing manual this
value for a bound function won’t change anything — still bound this
will be used:
var b = BoundF.call({z: 40}); alert(b.z); // still 30
The overloaded [[Construct]]
method (see 15.3.4.5.2) also delegates to the original [[Construct]]
which is defined in 13.2.2. However, there is one important note with using bound function as a constructor. The original [[Construct]]
to which delegates the overloaded [[Construct]]
of a bound function, calls then the original [[Call]]
of the target function, but not the [[Call]]
of the bound function. Which means that inside the initializing call of the function we do not have a bound this
value, but instead the this
value set to the newly created object.
This seems quite logical, though can confuse, because bound this
value does not matter in using the bound function as a constructor. This is important difference from the JavaScript bind
implementation e.g. in “Prototype.js” — there the bound this
value is used instead, which is not conforming to ES5. And returning this
value in “Prototype.js” implementation manually from a bound constructor, we will have the bound this
value. But if there is no manual return this
, then result of a bound constructor is indeterminate and dependent on returned value of the bound function.
In general case it is not possible to implement completely conforming bind
behavior with using bound function as a constructor on JavaScript. Although, some acceptable emulation of the new
bind
behavior is possible.
In built-in ES5 bind
implementation, having the same BoundF
from the example above, we have the following results:
// put something to the prototype // of newly created objects, notice, that we use // exactly "F" function, but not "BoundF" F.prototype.a = 40; // 15.3.4.5.2 [[Construct]] for bound functions var objectFromConstruct = new BoundF(20); alert([ objectFromConstruct.x, // 10, from [[BoundArgs]] objectFromConstruct.y, // 20, from extraArgs objectFromConstruct.z, // undefined, does not use [[BoundThis]] objectFromConstruct.a // 40, from F.prototype === objectFromConstruct.[[Prototype]] ]);
Notice again, that by default a bound function does not have a prototype
property — it is taken from the original (target) function. And even if we define this property on the bound function manually (a bound function object is extensible), it will change nothing, because, repeat, [[Construct]]
and [[Call]]
at the end are applied to the target function.
alert(BoundF.prototype); // undefined BoundF.prototype = { constructor: BoundF, a: 100, b: 200 }; var objectFromConstruct2 = new BoundF; // no matter, prototype still is taken from // F.prototype, but not BoundF.prototype, // because exactly F.[[Construct]] is called from // BoundF.[[Construct]] alert([ objectFromConstruct2.a, // 40, from F.prototype, but not 100 objectFromConstruct2.b // undefined ]);
So, regarding this value in bound function cases we have this summary:
- Simple call via
BoundF()
—boundThis
; call
/apply
, i.e.BoundF.call(manualThis)
— stillboundThis
;- as a constructor —
new BoundF()
— newly created object.
The overloaded [[HasInstance]]
method (see 15.3.4.5.3) also delegates to the original [[HasInstance]]
which is defined in 15.3.5.3 section. This internal method participates in the instanceof
operator, therefore it should return true
in testing with both — the original and the bound functions:
alert([ objectFromConstruct instanceof F, // true objectFromConstruct instanceof BoundF, // true ]);
Notice, that because of such delegation, instanceof
returns true
for testing an object created by the original constructor with the bound one:
var object = new F; alert([ object instanceof F, // true object instanceof BoundF, // true ]);
That also relates to using Object.create
method. Even if we pass manually created BoundF.prototype
as the first argument for Object.create
, instanceof
check returns false
for testing with bound and original constructors:
var boundProto = { constructor: BoundF, }; BoundF.prototype = boundProto; var foo = Object.create(BoundF.prototype); print(Object.getPrototypeOf(foo) === BoundF.prototype); // true // but because of overloaded [[HasIntance]], // which delegates to F.[[HasIntance]] we have false print(foo instanceof BoundF); // false print(foo instanceof F); // false // in contrast with non-bound constructor var bar = Object.create(F.prototype); print(bar instanceof BoundF); // true print(bar instanceof F); // true // However, if we set F.prototype // to the needed object, then // instanceof will work for object, // created by the bound constructor F.prototype = boundProto; print(foo instanceof BoundF); // true print(foo instanceof F); // true
Another feature of bound functions — they throws a TypeError
if there is an attempt to read the caller
or the arguments
properties on the bound functions, and this happens regardless whether the code is strict or not. In contrast, normal functions throws the same errors only in strict mode.
alert([ F.caller; // error only in strict F.arguments; // error only in strict BoundF.caller; // always error BoundF.arguments; // always error ]);
Constructor with various number of arguments
One interesting feature which is related with bound functions, is an ability to call a constructor function with various number of arguments. Thus, needed arguments are passed as an array. I.e. we get sort of Function.prototype.apply
but available for the applying with new
— in using the function as a constructor. A slightly modified example from MDC:
Function.prototype.construct = function (args) { var boundArgs = [].concat.apply([null], args), boundFn = this.bind.apply(this, boundArgs); return new boundFn(); } function Point(x, y) { this.x = x; this.y = y; } var point = Point.construct([2, 4]); console.log(point.x, point.y); // 2, 4
However, we should consider that such approach is not efficient, since every time we should create a new bound function for that, so the example is mostly academical.
Summary
So, we have the following key features of the bound functions created via the built-in bind
method:
- The main purpose is binding statically the
this
value and partial applications; - such functions are “lightweight” — they do not have
[[Scope]]
property (and therefore do not form closures), and also[[FormalParameters]]
,[[Code]]
andprototype
properties; - they delegate to the
[[Call]]
,[[Construct]]
and[[HasInstance]]
of the original function; - thus inside the
[[Call]]
methodthis
value is bound, but if the[[Call]]
is activated from the[[Construct]]
— not, there it refers to the newly created object; - independently from the strict mode such functions throw a
TypeError
exception for attempts of reading thecaller
or thearguments
properties on these functions.
If you have questions, I will be glad to answer them in comments.
Additional literature
ECMA-262-5:
Other useful articles:
Written by: Dmitry A. Soshnikov.
Published on: 2010-06-13
Честно говоря, это жесть.
Чем они думали, когда задавали такое поведение?
Это нарушение согласованности языка.
Превращают JS в треш.
I am completely misunderstanding the “Constructor with various number of arguments” section.
How interesting that bind() was implemented with lightness as one of the ideals. For years I’ve been using the following delegate() method (I have a C# background) in order to avoid the baggage that closures involve. It was worrying when arguments.callee was marked for deprecation. It’s good to see that using bind() is no compromise as it gives exactly the same semantics but with the efficiency of being native and the delegate not getting a prototype.
@devBunny, interesting solution, although keep in mind, that functions created via
Function
constructor do not save current scope chain, and always have only Global object in it.Alternatively, since ES6 one may use an arrow function which also captures
this
lexically.