12

consider:

let sel=document.getElementById('mys');

sel.onchange=function(e) {
  console.log(e.currentTarget===null); // false
  setTimeout(e => {
     console.log(e.currentTarget===null); // true
  }, 0, e);
 }
<select id="mys">
  <option value="volvo">Volvo</option>
  <option value="saab">Saab</option>
  <option value="mercedes">Mercedes</option>
  <option value="audi">Audi</option>
</select>
  • why does e.currentTarget changes after the timeout ? is that a browser (chrome) bug ?

  • how can I transfer an exact clone of the event to the timeout function ? I tried simple cloning but currentTarget is not writable and cannot be ovverridden ..

kofifus
  • 17,260
  • 17
  • 99
  • 173
  • Only use snippets for runnable code – Andrew Li Sep 22 '16 at 21:32
  • 2
    Looks like you need a closure: https://jsfiddle.net/vrfv6gy7/ – Andrew Li Sep 22 '16 at 21:44
  • 1
    interesting, is this a closure ? I thought a closure would be not passing e to setTimeout at all ... what you're doing is an IIFE ? why does that work ? – kofifus Sep 22 '16 at 21:55
  • [from MDN](https://developer.mozilla.org/en-US/docs/Web/API/Event/currentTarget): "[Event.currentTarget] identifies the current target for the event, as the event traverses the DOM". This is not a static property. It would make sense that after the event is finished bubbling, it would evaluate to `null`. Try using `event.target` instead. (Only commenting rather than answering because I don't know if I'm right or not.) – Joseph Marikle Sep 23 '16 at 21:04

4 Answers4

6

event.currentTarget and some other properties change after exiting the event handler.

Doing a context switch (setTimeout(... ,0);) in an event handler is common, it seems that the only way to correctly pass the event including currentTarget etc is to clone it as below.

Note that this is only 'good enough' ... the clone is not exactly the original event, and it's context is different ... doing for example clone.stopPropogation() is meaningless ...

If you need to change currentTarget etc on the clone delete !d.writable || !d.configurable || !d.enumerable ||

let sel=document.getElementById('mys');

function cloneEvent(e) {
 function ClonedEvent() {};
 let clone=new ClonedEvent();
 for (let p in e) {
  let d=Object.getOwnPropertyDescriptor(e, p);
  if (d && (!d.writable || !d.configurable || !d.enumerable || d.get || d.set)) {
   Object.defineProperty(clone, p, d);
  }
  else {
   clone[p] = e[p];
  }  
 }
 Object.setPrototypeOf(clone, e);
 return clone;
}

sel.onchange=function(e) {
 console.log(e.currentTarget);
 let clone=cloneEvent(e);
 setTimeout(e => {
  console.log(e.currentTarget);
 }, 0, clone);
}
<select id="mys">
  <option value="volvo">Volvo</option>
  <option value="saab">Saab</option>
  <option value="mercedes">Mercedes</option>
</select>
kofifus
  • 17,260
  • 17
  • 99
  • 173
  • It seems just assign it to a variable/const and use it in the setTimeout callback, and it works. `const ct = e.currentTarget`, and then use `ct` in the setTimeout callback. –  May 31 '18 at 07:14
6

Inspired by Joseph Marikle's comment, I found the real cause:

DOM Standard § 2.9. Dispatching events describes the algorithm, which I'm partially reproducing here:

To **dispatch** an event to a target […] run these steps:
[…]
5. If target is not relatedTarget or target is event’s relatedTarget, then:
    […]
    9. While parent is non-null:
        […]
        8. Otherwise, set target to parent and then:
            […]
        9. If parent is non-null, then set parent to the result of invoking parent’s get the parent with event.
        […]
    […]
    12. Set event’s eventPhase attribute to CAPTURING_PHASE.
    […]
    14. For each tuple in event’s path, in reverse order:
        1. […] invoke […]
    15. For each tuple in event’s path, in order:
        1. If tuple’s target is non-null, then set event’s eventPhase attribute to AT_TARGET.
        2. Otherwise, set event’s eventPhase attribute to BUBBLING_PHASE.
        3. […] invoke […]
[…]
6. Set event’s eventPhase attribute to NONE.
7. Set event’s currentTarget attribute to null.
[…]

To **invoke** […] run these steps:
[…]
5. Initialize event’s currentTarget attribute to struct’s invocation target.
[…]

So, after the event has finished processing all phases (capture, at_target, bubbling), then currentTarget gets assigned null.

Denilson Sá Maia
  • 47,466
  • 33
  • 109
  • 111
  • 1
    Wow. This is the first time I noticed that `currentTarget` is nulled. Why would they do that? – fregante Jun 23 '19 at 08:08
  • 1
    Because after the entire capture/at/bubbling phases have completed, there is no `currentTarget` anymore, because there is no target being processed anymore. – Denilson Sá Maia Jun 24 '19 at 12:19
  • 1
    Wow x2. I just found out that the same exact event object is passed around all event handlers. This means that you can append properties to `e` and they'll be accessible by the next handler. – fregante Jun 24 '19 at 14:12
  • @DenilsonSáMaia However `currentTarget ` is not `null`'ed with onMouseEnter, any explanation to that? – RegarBoy Nov 01 '21 at 11:30
-1

let sel=document.getElementById('mys');

sel.onchange=function({currentTarget}) {
  console.log(currentTarget===null); // false
  setTimeout(() => {
     console.log(currentTarget===null); // false
  }, 0);
 }
<select id="mys">
  <option value="volvo">Volvo</option>
  <option value="saab">Saab</option>
  <option value="mercedes">Mercedes</option>
  <option value="audi">Audi</option>
</select>
wenyi
  • 1,384
  • 8
  • 11
-4

I recommend you trying with other browsers as the code is working with mine, I don´t see any problem with the code snippet.