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

On Thu, Apr 29, 2010 at 3:01 AM, Alex Russell <[email protected]> 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
_______________________________________________
es-discuss mailing list
[email protected]
https://mail.mozilla.org/listinfo/es-discuss

Reply via email to