4

I saw this on Twitter and I couldn't explain it either. Defining a onload function in the following two manner works:

onload = function(){
 console.log('this works');
};

window.onload = function(){
 console.log('this works');
};

But when defined like the following, it doesn't work even though it is assigned to window.onload

function onload(){
 console.log('this does not work');
};
console.log(window.onload); // this shows the definition of above function

What's going on here?

VLAZ
29.6k9 gold badges65 silver badges88 bronze badges
asked Dec 10, 2012 at 18:29
0

3 Answers 3

9

THIS IS WRONG, PLEASE REFER TO THE NEWEST ANSWER BELOW

The first two examples assign a function to the window.onload property (window. is implicit in the first example). The onload property actually belongs to the prototype of window (conveniently called Window).

The third variant declares a new local function with the same name, and that function shadows the property from the prototype. This means, when you ask for window.onload, the engine finds the local version first, and gives up looking up the prototype chain. So alert(window.onload); does alert your function source. However, for the event handler to work, it would have to be assigned to the prototype object's onload property.

However, there is something odd going on: when you try to assign to a property that's inherited from the prototype, it shouldn't work, and an "own" property should be created on the object, shadowing the one from the prototype (e.g. http://jsfiddle.net/ssBt9/). But window behaves differently (http://jsfiddle.net/asHP7/), and the behavior may even vary depending on the browser.

answered Dec 10, 2012 at 18:31
Sign up to request clarification or add additional context in comments.

10 Comments

What does shadow mean, is it a technical term? I can't wrap my head around why it doesn't execute when it still can be referenced from window.onload. Can you add more explanation?
May as well add that functions declared in the global scope become properties of the window object, but it still shadows the original window's onload property. jsfiddle.net/Ad7JG/1
Turns out my answer was sort of correct. But I had to ask a separate question to realize that.
I don't think this is correct. In both first cases, you're also directly assigning the property of window, not on its prototype.
@dystroy I'm still researching this issue, but apparently window.onload = function(){} and onload= do assign to a property of the prototype, instead of creating an own property that shadows the one from the prototype. Host objects can behave differently sometimes, and window is behaving differently in this case. This is a possible explanation: w3.org/html/wg/drafts/html/master/…
|
3

That's because onload is already declared and null before your script executes.

This is similar to that code :

var v=null;
function v(){
 console.log('hi');
}​​​​
console.log(v); // alerts null

which is different from this one :

function v(){
 console.log('hi');
}​​​​
console.log(v); // alerts the function

When you declare a function like this, the declaration and assignment are logically hoisted to the "start" of the scope, so the assignment doesn't really occur after the onload function is given the null value.

That's why it's different from

window.onload=...

which isn't a declaration but only an assignment which can't be hoisted.

answered Dec 10, 2012 at 18:44

2 Comments

I'm still a little confused about this. So you're saying there's something like an implicit var onload = null that overwrites the hoisted function declaration?
Yes, the window object is initialized before you run your script. You can type this in the console : console.log(window, window.onload)
1
+250

Defining an object property and assigning to a property are two separate operations. While assignment to a var declarations at global scope may skip the former and only perform the latter on the global object, a global function declaration always creates bindings through defining and not assigning.

<script>
 Object.defineProperty(window, 'quux', {
 get() { console.log('quux GET'); return 42; },
 set(v) { console.log('quux SET %o', v); },
 configurable: true,
 });
</script>
<!-- split into multiple scripts to defeat declaration hoisting -->
<script>
 var quux = 42;
 console.log(
 'after var declaration: %o',
 Object.getOwnPropertyDescriptor(window, 'quux'));
</script>
<script>
 function quux() {}
 console.log(
 'after function declaration: %o',
 Object.getOwnPropertyDescriptor(window, 'quux'));
</script>

This was specified in ECMA-262 Ed. 5.1, §10.5 at the time the question was written: function declarations are covered by step 5, while var declarations by step 8. For a more contemporary version of the spec, see ECMA-262 15th Ed., §9.1.1.4.18 and §9.1.1.4.17 – it is almost the same.

Now, onload is an own, configurable accessor property of window:

console.log(Object.getOwnPropertyDescriptor(
 window, 'onload'));

It is through the setter of onload that a browser can hook up a function to be an event handler: the setter remembers the function object in internal, private state where it will be looked up when the event fires. When the property is redefined, which happens when a function is declared, access to that private state is lost, and window.onload becomes just like any other random global property.

Now, if onload were a prototype property, not even var would generate an assignment:

<script>
 Object.defineProperty(Window.prototype, 'quux', {
 get() { console.log('quux GET'); return 42; },
 set(v) { console.log('quux SET %o', v); },
 configurable: true,
 });
</script>
<script>
 quux = 42;
 // will be undefined – this is not an own property
 console.log(
 'after assignment: %o',
 Object.getOwnPropertyDescriptor(window, 'quux'));
</script>
<script>
 var quux = 42;
 console.log(
 'after var declaration: %o',
 Object.getOwnPropertyDescriptor(window, 'quux'));
</script>

Although this might have been not the case back in 2012: if we look closely at step 8 of ECMA-262 Ed. 5.1, §10.5 again, we see it checks for existing bindings through the HasBinding algorithm and if there isn’t one, it creates a new binding through CreateMutableBinding. For the global scope, HasBinding (§10.2.1.2.1) uses the [[HasProperty]] slot that also checks for properties inherited from the global object. But already in ES6, the algorithm CanDeclareGlobalVar (ECMA-262 6th Ed. §8.1.1.4.15) checks only for own properties of the global object, so inherited properties will be shadowed instead. Presumably, event-handler properties may have moved from Window.prototype to window itself to accommodate this change (and therefore keep var onload = function() { ... } working), but I have no way to check that, nor do I know where to look for committee discussions that would confirm this.

answered Dec 20, 2024 at 10:58

1 Comment

Yes, you seem to be correct. Unfortunately I can't delete my wrong answer because it's marked as accepted

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.