Compartilhar via


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.

Comments

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

  • Anonymous
    September 20, 2004
    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.

  • Anonymous
    September 20, 2004
    The comment has been removed

  • Anonymous
    September 20, 2004
    Sure, obviously the JScript object model implements the JScript rules! But the WSH object model does not.

  • Anonymous
    September 20, 2004
    The comment has been removed

  • Anonymous
    September 20, 2004
    The comment has been removed

  • Anonymous
    September 20, 2004
    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...

  • Anonymous
    September 20, 2004
    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.

  • Anonymous
    September 20, 2004
    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.

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

  • Anonymous
    September 20, 2004
    The comment has been removed

  • Anonymous
    September 20, 2004
    The comment has been removed

  • Anonymous
    September 20, 2004
    :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 ?

  • Anonymous
    September 20, 2004
    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.

  • Anonymous
    September 21, 2004
    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.

  • Anonymous
    January 21, 2007
    Hope all of you are doing great! Today I am going to discuss sort of limitation of Jscript engine which

  • Anonymous
    May 17, 2007
    There is a typo in your second last paragraph. 'mehtod'