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

Brendan Eich brendan at mozilla.org
Thu Sep 11 11:11:34 PDT 2008


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.


> 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.

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.


> 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.


> 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.

/be

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://mail.mozilla.org/pipermail/es-discuss/attachments/20080911/81548e9d/attachment-0001.html 


More information about the Es-discuss mailing list