three small proposals: the bikeshed cometh!

Brendan Eich brendan at mozilla.com
Thu Apr 29 12:30:57 PDT 2010


On Apr 29, 2010, at 11:44 AM, Garrett Smith wrote:

> On Thu, Apr 29, 2010 at 3:01 AM, Alex Russell <alex at dojotoolkit.org>  
> wrote:
>> From 15.3.4.5:
>>
>>   4. Let F be a new native ECMAScript object .
>>
>> I can't discern any particular reason why equivalence should succeed
>> for multiple calls to bind() from the spec. What am I missing?
>>
>
> The bound function that is returned can be saved as a property of
> something, so that it can be later removed.
>
> var f = obj.method.bind(obj);
> node.addEventListener("click", f, false);
> node.removeEventListener("click", f, false);

Sure, you can save the bound method reference. It should remain a  
first-class function -- this is in contrast to languages with only  
methods on classes and no way to extract them as funargs to pass  
around, rather than invoke (even .apply wants an extracted funarg).

This raises a point I mentioned in https://mail.mozilla.org/pipermail/es-discuss/2010-February/010830.html 
  et seq., namely that bound methods are not enough to avoid a million  
moveTo methods if you have a million Sprite object instances, each  
method bound to its instance as receiver (|this|):

   let sprites = [];
   for (let i = 0; i < 1e6; i++)
     sprites[i] = new Sprite(i);

If Sprite is written conventionally:

   function Sprite(i) {
     let sprite = {index: i, ...};
     sprite.moveTo = (function (x,y) {...}).bind(sprite);
     return sprite;
   }

then implementations burn a lot of unjoined function objects just to  
associate |this| with each sprite in sprites[i].moveTo(x, y).

Classical OOP languages can avoid this in the call-expression use case  
(where no funarg is extracted). Even ignoring static typing or type  
inference, if the callee Reference base is the object from which the  
method identified by the Reference propertyName was extracted, then no  
new function object need be allocated to associate that receiver with  
that method (ignoring arguments.callee and other identity-leaking  
hazards in JS).

How could the callee type be wrong? In JS, it could be due to funarg  
extraction:

   let f = s.moveTo; ...; f(x, y)

will bind |this| to the global object (non-strict) or undefined  
(strict).

   let o = {moveTo: s.moveTo};
   ...
   o.moveTo(x, y)

or

   s.moveTo.call(o, x, y)

likewise would try to bind |this| to o when activating moveTo. Any  
scheme to prevent these receiver override attempts requires allocating  
a function object that carries s along with moveTo at the point of  
method extraction, if not earlier in the lifetime of s.

Alex et al.'s ! proposal would let the user decide whether to bind  
early or late, by letting one use . instead of ! at the point of  
reference, not at the point of method definition. On the other hand  
this proposal complicates the surface language (which do I use, "." or  
"!"? asks the average programmer) and requires on the fly memoization.

So with current JS implementations, whether using the ES5 built-in  
Function.prototype.bind or a bind progenitor from a JS library,  
naively binding every method to each instance in a constructor forces  
a new unjoined function object for every method times every instance.  
This not only can cost too much, it may break a wanted method-identity  
property that putting the method on a common prototype object preserves.

This is a case where new syntax at the method definition site can  
help. Then if one does

   function Sprite(i) {...}
   Sprite.prototype = {
     method moveTo(x,y) {...}    // <- new syntax here
   };

the price of allocating an unjoined function object to associate  
moveTo with s will be incurred, and identity diverges, only if one  
extracts the method to call later, or tries to override |this| via  
apply or call.

But the common case of s.moveTo(x, y) for all one million Sprite  
instances pays no such price and preserves the prototype-homed method  
identity.

/be


More information about the es-discuss mailing list