First, you ask:
why is hashchange called for the first time the first execution? Doesn't this script only change the hash without warning?
To answer this question, we can delve into the specification . When switching to a new fragment (i.e., setting document.location.hash ), the specification goes through several steps, one of which is:
- Move the story to a new record with the asynchronous event flag set . This will scroll to the fragment identifier specified in what is now the address of the document.
The story traversal description says:
- If the asynchronous event flag is not set, follow these steps synchronously. Otherwise, the asynchronous event flag is set; queue for the following substeps .
- If the state is changed correctly, fire up a trusted event named popstate in the Window object of the document using the PopStateEvent interface, with the state attribute initialized with the state value. This event should bubble, but not be canceled, and has no default action.
- If the hash is changed, thatβs true, then fire up a trusted event named hashchange in the context of the viewport. Window object using the HashChangeEvent interface, with the oldURL attribute initialized by the old URL and the newURL initialized attribute to the new URL. This event should bubble, but not be canceled, and has no default action.
So, all this together means that when you run your code, an event receiver for hashchange will be added before the code is executed in the sub-steps in step 14 and subsequently will be launched when the hash is set.
How can I fix it so that it works as described?
To fix this, you can also add an event listener to the queue using setTimeout(.., 0) :
setTimeout(function() { $(window).on('hashchange', function() { alert('hello'); }); }, 0);
Since you add this to the queue after installing the hash, it will be added to the queue after the task queued in step 14 above, and thus the event listener is added only after the event has been fired.
source share