Fabulous Adventures In Coding

Eric Lippert's Blog

Use vs. Mention in JScript Doesn't Come For Free

Before today's entry, a quick note. Work has just gotten insanely busy as we push towards getting VSTO ready for the Whidbey release. I likely won't have much time to blog over the next couple weeks, so the blog refresh rate is going to go down for a while. I have a collection of pre-written articles -- such as this one -- that I'll dip into every now and then when I have a few spare minutes, but I likely won't be very topical or responsive for a bit.

OK, onward; a while back a reader asked what the difference was between

document.write("hello");

and

foo = document.write;
foo("hello");

from the point of view of what actually happens "behind the scenes" in the IDispatch calls.  The reader noted in particular that though this trick works in IE, it doesn't work in WSH:

foo = WScript.Echo; // Nope, sorry.
foo("hello");

Here's the deal.  The IE object model has been specially designed with JScript in mind.  Unlike VBScript, JScript makes a distinction between naming a function and calling a function.  When you say

abc = blah.baz();

that calls the baz function and results in whatever it returns.  But

abc = blah.baz;

assigns the baz function object itself to abc; it essentially makes an alias.

The IDispatch interface takes flags which determine whether the caller wants to fetch the named property or call the named function. Many object models do not honour those flags though, because no language before JScript really took advantage of the distinction.  In fact, object models designed to be called from VB or VBScript often don't even check the flag -- if it says "fetch the property" or "call the method", well, it just calls the method either way.

So here's what happens when you say foo = document.write;  First, JScript attempts to resolve "document".  It can't find a local or global variable called that, so it asks the global window object for the document property.  IE gives back the document object.  JScript then asks the document object to give back the value of the write property.  IE creates an object which has a default method.  The default method calls the write function, but no one calls the method yet -- we just have an object which, when invoked, will call the mehtod.  JScript assigns the object to foo.  Then when you call foo("hello"); JScript invokes the default method on the object, which calls the write method.

The WSH object model was not designed with this in mind.  It does not make a distinction between naming a function and calling it, so you can't use this trick.  WScript.Echo; does not give back a function object that can be invoked later.

Published Monday, September 20, 2004 10:16 AM by Eric Lippert

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

 

Jerry Pisk said:

So the WSH objects are ignoring the IDispatch call parameters? Should code like that be allowed to be called a COM object?
September 20, 2004 10:47 AM
 

Eric Lippert said:

Obviously it's a COM object -- it obeys all the rules of COM. The question is whether it is a good Automation object. And sure, an automation object is allowed to implement any semantics it wants. If an automation object decides that calling a method when it is invoked as a property get is the right thing to do, that's the object author's prerogative.

The WScript object model isn't the unusual one; most object models behave like that because most object models are called from VB/VBScript, which does not make the use-mention distinction for functions. My point is that it's the IE object model which is the weird one.
September 20, 2004 10:56 AM
 

zwetan said:

don't want to be picky here but aliasing is a natural behaviour of ECMAScript and this work also in WSH but only for non build-ins I suppose.

foo = {};
foo.bar = {};
foo.bar.hello = function()
{
return "world";
}

myAlias = foo.bar.hello;

WScript.Echo( myAlias() ); //aliasing work

WScript.Echo( (foo.bar.hello === myAlias).toString() ); //true


I don't know all the subtleties of IDispatch but it make me think of a similar problem with IE where you can't add properties using prototype to IE build-ins DOM object.
September 20, 2004 12:10 PM
 

Eric Lippert said:

Sure, obviously the JScript object model implements the JScript rules! But the WSH object model does not.

September 20, 2004 12:15 PM
 

Dan Shappir said:

As Eric obviously knows one problem with using functions as aliases for methods in this way is that you loose the object context. That is, when document.write is invoked 'this' is a reference to the document object. OTOH when foo is invoked 'this' reverts to the global object. In this case it doesn't matter because document.write is a "static" method. I've put static in quotes because JavaScript doesn't have such a keyword. Instead a method is made static by simply ignoring the value of 'this'. If a non-static method had been used, such as document.focus, the appropriate context would have been required, forcing the use of the call or apply Function methods.

This is actually somewhat annoying because browser event handlers are functions. If you assign an object method to a callback such as onclick, when the callback is invoked 'this' is a reference to the object which generated the event rather than the method's owner. In the BeyondJS library we introduced the Function.from mechanism to handle this scenario:

foo = Function.from(document, "focus");
foo(); // it works
September 20, 2004 2:58 PM
 

Eric Lippert said:

Indeed, that's an excellent point which I forgot to mention.

The "this" semantics are thoroughly screwed up in JScript. IIRC, it gets even more messed up when you consider separate modules, but I haven't looked at that code for so long that I don't recall the exact semantics.


September 20, 2004 3:04 PM
 

Jerry Pisk said:

So a COM object can ignore the parameters of IDispatch::Invoke? I never knew that, I always thought that I should return E_NOTIMPLEMENTED or maybe DISP_E_MEMBERNOTFOUND when asked to do something I don't support, not just silently default to a different action than what was requested. I would've saved myself tons of work if I knew I can ignore the request parameters and just do what I thought is the easiest...
September 20, 2004 4:26 PM
 

Eric Lippert said:

Hold on, I'm not saying that it's a free-for all. It depends on what you're doing.

You'd return DISP_E_MEMBERNOTFOUND if someone tried to pass in a dispatch identifier you didn't recognize. That makes sense -- it is almost certainly an error condition.

You'd return E_NOTIMPLEMENTED if someone called a method that you simply hadn't implemented -- like, say you don't want to return type infos.

But what we're talking about here is that some caller passes in the flag for "call this method" when in fact there is no "method" at the given slot, there's a property getter. You could politely call the property getter rather than rudely returning an error. That's certainly well within the bounds of propriety.

My sole point is that an object model is not _required_ to have different behaviour for "fetch me this property" and "call me this method". JScript was the first automation language that actually made a distinction between the two, and therefore object models that were designed with VB in mind unsurprisingly don't act like JScript objects.
September 20, 2004 4:32 PM
 

yn said:

Shappir: that is true for most browsers, but it's not true for IE. In IE the function reference actually maintains context, this most likely has to do with the specific handling of this situation as Eric mentioned. The newly created object knows its initial context and apparently saves it.

Try this:

attachEvent("onload",yourElement.focus);

Or if that bothers you, the semantically equiv:

var fRef=yourElement.focus;
attachEvent("onload",fRef);

You will see that focus is correctly moved to the appropriate element.

This (very comfortable) feature isn't available in other browsers, which follow JS rules a little more closely in the DOM.
September 20, 2004 7:54 PM
 

yn said:

Just a note: I was only referring to native DOM function references, of course. JS function references WILL lose their "this", obviously.
September 20, 2004 7:57 PM
 

Dan Shappir said:

yn:
> You will see that focus is correctly moved to the appropriate element.

I was surprised enough by your example to actually try it, and yes you are correct, the focus() method does maintain the appropriate context. But I think this has to do with how focus() is implemented internally rather than how attachEvent is implemented. Consider the following example that uses attachEvent with a JScript object:

<html>
<head>
<script>
text = "bye bye";
var foo = {
bar : function() { alert(this.text); },
text : "hello world"
};
window.attachEvent("onload", foo.bar);
</script>
</head>
<body>
hello
</body>
</html>

If attachEvent maintained proper context the text "hello world" would have been displayed. But instead the text "bye bye" is displayed.

As Eric pointed out the "this" semantics are thoroughly screwed up in ECMAScript.
September 21, 2004 12:15 AM
 

Jonathan Perret said:

I like to think of ECMAScript function pointers as behaving just like C function pointers, i.e. without an instance pointer.

Dan, if you follow closely what Eric explains in his post as to how getting the "property" 'write' on 'document' is implemented, you'll read that 'document.write' does _not_ return a JScript function pointer (as your foo.bar does) but a temporary COM object with a default method. Presumably this object holds an instance pointer as well, which explains the behavior observed for focus() as well. Btw I don't believe document.write() could work as a 'static' as you describe.

What I use when I want such delegate-like functionality with JS functions is a simple anonymous function :

window.attachEvent("onload", function() { return foo.bar() });

Presumably that's close to what your Function.from() does... No runtime parsing is involved though.
September 21, 2004 3:20 AM
 

zwetan said:

:S I don't see any problem with the behaviour of loosing the object context when using function as aliases, it's the function which is aliased not the object.

from ECMA-262:
"The pop function is intentionally generic; it does not require that its this value be an Array object.
Therefore it can be transferred to other kinds of objects for use as a method. Whether the pop
function can be applied successfully to a host object is implementation-dependent."

Imho the intentional genericity of function aliasing is the default behaviour right ?
September 21, 2004 4:32 AM
 

Dan Shappir said:

Jonathan:
JavaScript is a pure OO language - everything is an object, including functions. Thus while JavaScript function references appear similar to C function pointers, they are really very different beasts. C function pointers are basically just memory addresses, with some compile-time metadata used to validate arguments and the return value. JavaScript functions are objects that contain more information, e.g. they are closures.

JScript is built on top of COM automation, and as a results implements objects using IDispatch. This includes function objects. So when you ask for a reference to document.write, or any JScript function, you get an IDispatch that encapsulates that function, which you invoke using DISPID_VALUE (the “this” value is passed using the named argument DISPID_THIS BTW).

Function.from is implemented using closures. For more information on this technique see http://w3future.com/html/stories/callbacks.xml

zwetan:
The technique you mentioned is useful and indeed can be used to implement something sort of like aspects. OTOH I sometimes do want to keep the object context. Fortunately I can use closures for this, though I believe this technique is not well known or understood by most JavaScript programmers.
September 21, 2004 5:43 AM
 

yn said:

Shappir: Of course it doesn't have to do with attachEvent, my whole point was DOM methods. What you did in your example was show that JS function references lose their object context, that's obvious and I even commented on it right after my initial comment:

> Just a note: I was only referring to native DOM function references, of course. JS function references WILL lose their "this", obviously.

Again, the point was that method references to DOM methods retain object context (most likely because of the way they were implemented according to Eric - creating a new object), JS methods do not retain object context, which you just (re)showed.
September 21, 2004 3:07 PM
 

Fabulous Adventures In Coding said:

October 7, 2004 1:38 PM
 

Jaiprakash said:

Hope all of you are doing great! Today I am going to discuss sort of limitation of Jscript engine which

January 22, 2007 6:54 AM
 

Tanveer Badar said:

There is a typo in your second last paragraph. 'mehtod'

May 18, 2007 6:34 AM

Leave a Comment

(required) 
(optional)
(required) 

  
Enter Code Here: Required
Submit

About Eric Lippert

Eric Lippert is a senior developer on the Microsoft C# compiler team. Before that he worked on the framework of Visual Studio Tools For Office. Before that, he worked on the compilers, runtimes and tools for VBScript, JScript, Windows Script Host and other Microsoft Scripting technologies. He lives in Seattle and spends his free time editing books about programming languages, playing the piano, and trying to keep his tiny sailboat upright in Puget Sound.

This Blog

Syndication


© 2009 Microsoft Corporation. All rights reserved. Terms of Use  |  Trademarks  |  Privacy Statement
Microsoft
Page view tracker