I'm in a situation, where I kind of have to write my own event bubbling. Therefore, I need to pass a given Event-Object (created originally from the Browser/DOM) to multiple functions/instances, but I need to change the .target property for instance.
Problem: The property descriptors of almost all attributes deny any mutation, deletion or change. Furthermore, it seems not to be possible to clone or copy the whole Event-Object using Object.assign or read in the attributes using Object.getOwnPropertyNames or Object.keys.
It is of course possible to manually copy all properties into a new object by assigning them, but that seems pretty foolish.
I tried to be really smart and just use the original event-object as prototype of a new object, to just shadow the properties I want to mutate. Like
Object.setPrototypeOf( customEvent, event );
But if I do that, any access to properties of customEvent throw with:
Uncaught TypeError: Illegal invocation
It does seem to work if we use a Proxy-handler which also can be used to shadow certain properties. The only problem I have here is, that it is still not possible to execute functions like stopPropagation over the Proxy (Uncaught TypeError: Illegal invocation again).
Question: What is the best practice to manipulate/extend a DOM Event Object?
You will suffer from 7 years of misfortune if you mention jQuery in any form or shape.
I have used a Proxy for this problem, combined with a wrapper. 
First, setup a function that acts as a wrapper around a proxy. The wrapper receives an Event object and sets its value for later use. The reason for this will be explained further down.
function AdaptedEvent(ev) {
  const initialEvent = ev;
  let newTarget = 'your new target'; // can be dynamic if you wish.
  // returning with a handler for proxy
  return {
    get: (target, prop) => {
      // proxy'ing stuff
    }
  };
}
then inside the proxy, I have
get: (target, prop) => {
  if (prop === 'target') {
    return newTarget ? newTarget : initialEvent.target;
  }
  return target[prop];
}
when .target is called on your proxy object, it will do the statements described in the above if-body. It will return with a new target instead of the event's initial target.
Once you have set up your proxy, you have to construct a proxy object when handling a click event. Like
document.querySelector('#foo').addEventListener(
  'click',
  e => handleClick(new Proxy(e, new AdaptedEvent(e)))
);
Example:
function AdaptedEvent(ev) {
  const initialEvent = ev;
  let newTarget = 'your new target';
  return {
    get: (target, prop) => {
      if (prop === 'target') {
        return newTarget ? newTarget : initialEvent.target;
      }
      return target[prop];
    }
  };
}
function handleClick(event) {
  console.log(event.target);
  console.log(event.which);
}
// binding click event.
document.querySelector('#foo').addEventListener('click', e => handleClick(new Proxy(e, new AdaptedEvent(e))));
<button id="foo">click me!</button>
But as you have mentioned an issue that I have seen too when trying it out:
It does seem to work if we use a Proxy-handler which also can be used to shadow certain properties. The only problem I have here is, that it is still not possible to execute functions like
stopPropagationover the Proxy (Uncaught TypeError: Illegal invocation again).
In the example here below; I have replaced a button to a link. Let's say I want to stop its bubbling by using .preventDefault(). You can see that it will throw the mentioned error.
function AptEvent(ev) {
  const initialEvent = ev;
  let newTarget = 'your new target';
  return {
    get: (target, prop) => {
      if (prop === 'target') {
        return newTarget ? newTarget : initialEvent.target;
      }
      return target[prop];
    }
  };
}
function handleClick(event) {
  event.preventDefault();
  console.log(event.target);
  console.log(event.which);
}
// binding click event.
document.querySelector('#foo').addEventListener('click', e => handleClick(new Proxy(e, new AptEvent(e))));
<a id="foo" href="www.google.com">click me!</a>
The reason for this is that when calling the said function, you have lost the right context to call preventDefault. A solution for this is to use .bind. But what should you pass to the .bind? Yes, the native Event object. That is why I have setup the above AdaptedEvent function that receives an Event object as argument. Now you can pass the object when the call to a property is a function (easily being checked).
When you click on the link, the event bubbling got stopped.
function AdaptedEvent(ev) {
  const initialEvent = ev;
  let newTarget = 'your new target';
  return {
    get: (target, prop) => {
      if (Object.prototype.toString.call(target[prop]) === '[object Function]') {
        return target[prop].bind(initialEvent);
      }
      else if (prop === 'target') {
        return newTarget ? newTarget : initialEvent.target;
      }
      return target[prop];
    }
  };
}
function handleClick(event) {
  event.preventDefault();
  console.log(event.target);
  console.log(event.which);
}
// binding click event.
document.querySelector('#foo').addEventListener('click', e => handleClick(new Proxy(e, new AdaptedEvent(e))));
<a id="foo" href="www.google.com">click me!</a>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With