16

I've had a problem with this little snippet:

<script>
function download() {
    alert('Hi');
}
</script>
<a href="#" onClick="javascript:download();">Test</a>

Once I click on the link in Chrome 14.0, I get a

Uncaught TypeError: string is not a function

in Firefox and IE it works just fine. I solved the problem by renaming the function but I'm still curious what's with the "download" thing in Chrome. It's not a reserved keyword as far as I know so what might it be?

Amati
  • 1,484
  • 1
  • 16
  • 29
  • Do you have any extensions installed? –  Oct 21 '11 at 16:14
  • 4
    Confirmed. Even removing the `javascript:` that shouldn't be there doesn't help. Weird! – Lightness Races in Orbit Oct 21 '11 at 16:15
  • 1
    Don't use global functions. Place your functions in a namespace and bind the event handler programmatically. – Šime Vidas Oct 21 '11 at 16:15
  • @Muu, I have plenty but even if I start an incognito window (which apparently disables extensions) I still get the error. Also, as Tomalak pointed below, console.log(download) gives undefined. – Amati Oct 21 '11 at 16:24
  • @Amati: Where are you doing this? If you created a jsFiddle, note that the actual code is run in an iframe and global variables are not accessible from the console. – Felix Kling Oct 21 '11 at 16:27

2 Answers2

21

<a> elements have a download attribute in HTML5 as explained here, with a default value of "" (an empty string).

This means that download === this.download in the onclick handler (this is the element in onevent attributes), and therefore the download attribute of the element is superior to the download property of window.

This fiddle lists all string attributes that are present by default. You can see download is an attribute just like innerHTML, which also fails with the exact same reason when used as a function (i.e. trying to refer to window.innerHTML, but instead executing elem.innerHTML()).

As said in the comments, using window makes for no confusion as to what property/attribute variables will evaluate to.


This scope behaviour does actually not seem to due to the this value but rather a specific "scope chain" that is being constructed.

As per the HTML5 specification:

Lexical Environment Scope

Let Scope be the result of NewObjectEnvironment(the element's Document, the global environment).

If the element has a form owner, let Scope be the result of NewObjectEnvironment(the element's form owner, Scope).

Let Scope be the result of NewObjectEnvironment(the element's object, Scope).

I.e. what is happening is the scope chain is window -> document -> element (increasing superiority). This means that download evaluates to element.download and not window.download. What also can be concluded from this is that getElementById will bubble up to document.getElementById (given elem.getElementById does not exist).

I set up a systematic example so that you can see how variables bubble up the scope chain:

window.a   = 1;
document.a = 2;
elem.a     = 3;

window.b   = 4;
document.b = 5;

window.c   = 6;

Then, <a ... onclick="console.log(a, b, c)"> logs 3, 5, 6 when clicked.

Community
  • 1
  • 1
pimvdb
  • 151,816
  • 78
  • 307
  • 352
  • I don't like this weird scope aggregation thing, or whatever you want to call it... the same with forms. +1 – Felix Kling Oct 21 '11 at 16:25
  • So does that suggest that this could be fixed by changing it to `onClick="window.download()"`? – Joe White Oct 21 '11 at 16:25
  • @Joe White: Yes, `window` is always the same thing everywhere on the page. `this` isn't, what is causing the problem. – pimvdb Oct 21 '11 at 16:27
  • Interesting. I knew jQuery bound `this` to the element, but I never knew that browser events also did this by default. Unexpected to those of us who have used Delphi/WinForms/WPF/Silverlight, where all the event handlers are methods of the window so `this` is always the window. – Joe White Oct 21 '11 at 16:30
  • 3
    @Joe: `this` is not the problem here. That `this` refers to the element should be known... the problem is that the *scope* of the event handler does not only contain the global scope, but also the element itself as scope. It is as if someone did: `with(theElement) { theElement.onclick = function() {...} }`... that is the confusing part. – Felix Kling Oct 21 '11 at 16:35
  • N.B: And until now I have not found anything that specifies this behaviour.... any references are welcome :) – Felix Kling Oct 21 '11 at 16:42
  • 2
    @FelixKling: [Here's a reference.](http://docstore.mik.ua/orelly/webprog/jscript/ch19_01.htm#jscript4-CHP-19-SECT-1.6) *"Event handlers defined as HTML attributes have a more complex scope chain. The head of the scope chain is the call object... The next object in an event handler's scope chain isn't the global object, however; it is the object that triggered the event handler."* – user113716 Oct 21 '11 at 16:54
  • Thanks pimvdb! Makes sense now. Apparently other browsers haven't implemented the 'download' attribute yet. If I try renaming the function to a widely supported attribute, all browsers show error. – Amati Oct 21 '11 at 16:56
  • @Ӫ_._Ӫ: Thanks (I think I even read this already but forgot it ;)), but is there also a standard describing this? *Edit:* Nevermind, further down it says it was never standardized. – Felix Kling Oct 21 '11 at 16:56
  • @Felix Kling: Here is another: http://www.w3.org/TR/html5/webappapis.html#event-handler-attributes. "The user agent must set the thisArg (...) to the event handler's object.". It's the HTML5 specification. – pimvdb Oct 21 '11 at 16:58
  • @primvdb: What you referring to is again what `this` will refer to (to the element), but it does not explain how the scope chain is created. – Felix Kling Oct 21 '11 at 17:02
  • @pimvdb: Hmmm... That seems to deal with setting the calling context rather than defining the scope chain. EDIT: Felix was quicka. :) – user113716 Oct 21 '11 at 17:02
  • 2
    @Felix Kling & @Ӫ_._Ӫ: Sorry, this seems closer: http://www.w3.org/TR/html5/webappapis.html#event-handler-attributes. "Let Scope be the result of NewObjectEnvironment(the element's object, Scope)." So that would mean that the element itself is pushed before any other scopes (e.g. the window). This also makes sense since `document.xxx` functions seem available without using `document` inside a handler attribute as well (`document` is also pushed into the scope chain as per the rules there): http://jsfiddle.net/pimvdb/X5syU/1/ – pimvdb Oct 21 '11 at 17:08
  • @pimvdb: Yes, good find (bookmarked) :) So they finally decided to put this in specification. But I still think that this is a very confusing feature. Having DOM elements in the scope chain just feels wrong somehow... at least for now ;) – Felix Kling Oct 21 '11 at 17:11
  • @Felix Kling: Don't mind it, you don't define events in HTML anyway do you? :) – pimvdb Oct 21 '11 at 17:12
  • True... but that makes the different ways of attaching event handlers even more inconsistent... imo there should not be much of a difference between adding an event handler inline or via the DOM property... anyways, that's not a topic to be discussed in comments. Good answer and thanks for the HTML5 reference! :) – Felix Kling Oct 21 '11 at 17:16
0

Some function names are simply reserved or already used. Another would be "evaluate".

I recommend prepending something to all of your function and variable names to avoid this kinds of situations. Example: "sto_download"

Derek
  • 4,864
  • 5
  • 28
  • 38