Coercing 'this' (was: Topic list - pending changes and issues for the ES3.1 spec)

Mark S. Miller erights at google.com
Mon Sep 15 17:54:17 PDT 2008


On Thu, Sep 11, 2008 at 11:11 AM, Brendan Eich <brendan at mozilla.org> wrote:

> On Sep 9, 2008, at 11:11 AM, Mark S. Miller wrote:
>
> On Tue, Sep 9, 2008 at 9:21 AM, Mark S. Miller <erights at google.com> wrote:
>
> What should be the rules for coercing non-object 'this' values?
>
>
> In a previous thread we've already settled that ES3.1-strict will not
> coerce null or undefined 'this' values. In order to do this, we're
> going to migrate the spec language for coercing a null-or-undefined
> 'this' from the caller-side that scattered all over the spec (where
> it's hard to make it conditional on strictness), to the callee side in
> section 11.1.1.
>
>
> Great, this improves the spec.
>

Good. We seem to have no disagreement on this point.



>
> For non-strict code, this should make no observable
> difference.
>
> Some open questions:
>
> The ES3 spec language uses null as the implicit 'this' value
> associated with calling a function as a function. However, since the
> current spec language also coerces both null and undefined to the
> global object, it is unobservable whether null or undefined is used as
> the implicit 'this' value. In ES3.1 strict this difference becomes
> observable. In the interests of explaining 'this'-binding as being
> more like parameter-binding, I would like to see this changed to
> undefined. Calling a function as a function is like calling it without
> an argument for its 'this' parameter. I think this is more intuitive.
>
>
> This is strictly more useful than the current mess, and David-Sarah made
> good use of it in his Date self-hosting for the Date constructor function,
> so it can tell when it's called as a function. But it's still not enough in
> general:
>
> var funnyObj = {method: Date}
> funnyObj.method(2008,9,11)
>
> would bind funnyObj to |this| in Date, which would flow into the
> constructor-case code as the new Date object to initialize.
>

Agreed that thsi proposal does not prevent this existing confusion. Neither
does it make it worse. If there's something we can plausibly do to fix this,
I'd love to! Any suggestions?



> This is also badly incompatible. Global functions are used as methods of
> their window objects. Calling them via this.func() or otherFrame.func()
> works, but so do calls to func() from the same window.
>

Since there's only one window per JS global object (obviously, since they're
the same object), all the methods of the window could already be bound to
their window, and so not care what their 'this' is. However, that would
break code such as 'window1.func.call(window2)'. Currently, I expect func
will operate on window2; but if func is already bound to window1 it will
still operate on window1. Is this change of behavior a problem?


>
> When a primitive non-object type (number, boolean, string, presumably
> decimal) is passed as a 'this' value, the ES3 spec coerces it to a new
> corresponding wrapper object each time. This fresh allocation is
> observable and therefore expensive.
>
>
> This has been covered in past threads here, and in ES4 work. The relevant
> quote from
>
> http://www.ecmascript.org/es4/spec/incompatibilities.pdf
>
> is this:
>
> The wrapping of primitives on calls to methods that do not have bound-this
> types might at first glance
> appear to reintroduce the very problem we were trying to avoid, but this is
> not so. The predefined String,
> Number, and Boolean prototype methods in ES4 all have bound-this types
> (the types are Object,
> AnyNumber, and AnyBoolean, respectively), so no wrapping occurs on calls
> to predefined methods – this
> optimization is also performed in some existing ES3 implementations.
> Furthermore, wrapping can be
> performed lazily; it does not have to occur until a method references its this
> value.
>
> This is what some implementations in fact do. For example, SpiderMonkey
> passes primitive |this| to native functions that announce they're ready for
> this (relatively recent, in the long history of the API) change to the type
> of |this|, and we have a bug open to do likewise for scripted functions,
> with lazy wrapping as needed.
>
>
> IIRC, the ES4 spec avoids requiring
> this allocation, as that was felt to be an incompatibility everyone
> could live with.
>
>
> No, see above. ES4 (and real implementations that use primitive types as
> ES4 proposed here) does not break compatibility here. The "built-in"
> functions that are methods of the primitives work because string.prototype
> === String.prototype, e.g. -- no string wrapped in new String required for
> |this| when calling such prototype-delegated methods. Scripted functions
> added to String.prototype do require wrapping, but as the doc notes it can
> be done lazily.
>
> The trac ticket
>
> http://bugs.ecmascript.org/ticket/281
>
> notes that lazy wrapping must preserve identity.
>
>
> I agree. I propose that primitive 'this' values not
> be coerced at all.
>
>
> This is a pretty big incompatibility. I'll scan some Ajax libraries to look
> for obvious breaks. I suspect they'll be easy to find.
>

Any results?

If the answer is that we can't avoid wrapping unconditionally, then I
propose that we wrap only on entry to non-strict functions, where this
wrapping is again cetralized in the spec to section 11.1.1. Elsewhere you've
written "more carrots, less sticks", with which I heartily agree. If
"strict" suppresses non-stratified reflection that prevents many useful
optimizations (with, delete <var>, arguments.caller, Function.caller,
Function.arguments) and removes pointless and costly wrapping, then the
common wisdom may become "Make your program work under strict mode so it'll
be faster."



>
> If primitive 'this' values are no longer coerced, we can still explain
> the semantics of property lookup of an expression like 'foo.capture()'
> by saying that the property is looked up in the wrapper's prototype.
>
>
> ES4 did this by uniting string.prototype and String.prototype, etc.
>
>
> Or we could say that the property is looked up after wrapping, but a
> method call proceeds with the original unwrapped value. In other words
>
>     "foo".capture()
>
> should be equivalent to
>
>     Object("foo").capture.call("foo")
>
> given the original bindings for Object and Function.call.
>
>
> This much is compatible, provided the right wrapper is used (the one from
> the current execution context's scope chain's final object), since there is
> no string constructor or string.prototype.
>

Yes. After ES3.1, I'd like to shift to your way of explaining it, in terms
of all values being objects. If we do this right, this shift should only be
a better way of explaining the same behavior.


-- 
   Cheers,
   --MarkM
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://mail.mozilla.org/pipermail/es-discuss/attachments/20080915/5a2ac1d8/attachment-0001.html 


More information about the Es-discuss mailing list