JavaScript事件对象的克隆

20

有人知道如何深度拷贝/克隆本机JavaScript事件对象吗?我知道可以创建一个新的事件对象,并手动设置适当的属性以匹配原始事件,但如果有一种方法只是克隆,那将会更容易。


我还应该指出,一旦你掌握了事件对象,你几乎可以对它进行任何操作,而不会引起任何问题。它只是一个简单的对象,由本地代码构建并传递到处理程序中,并且没有任何依赖关系。在<= IE8的情况下,window.event只是一个事件对象的架子,每当处理新事件时就会交换对象。如果你在旧版IE中的其他地方引用window.event对象,假设它是一个典型的对象引用,那么在window.event被替换时,你应该保留原始对象。 - Erik Reppen
1
相关:https://dev59.com/qGcs5IYBdhLWcg3wjUuR - vsync
请翻译以下关于编程的内容,从英文到中文。只返回翻译后的文本:相关链接:https://stackoverflow.com/q/35215015/104380 - vsync
请不要使用下面的方法,而是按照上面链接的答案中所描述的那样使用 new MouseEvent()。答案链接为:https://dev59.com/qGcs5IYBdhLWcg3wjUuR#32670713 - Andrew
请注意,还有用于滚动的“WheelEvent”,而“MouseEvent”对此无法正常工作:https://developer.mozilla.org/zh-CN/docs/Web/API/WheelEvent - Andrew
4个回答

14
上述代码无法正确复制任何getter/setter。尝试:
function cloneEvent(e) {
    if (e===undefined || e===null) return undefined;
    function ClonedEvent() {};  
    let clone=new ClonedEvent();
    for (let p in e) {
        let d=Object.getOwnPropertyDescriptor(e, p);
        if (d && (d.get || d.set)) Object.defineProperty(clone, p, d); else clone[p] = e[p];
    }
    Object.setPrototypeOf(clone, e);
    return clone;
}

改进建议:clone = new Function() - vsync
同时,以下代码已足够:if (!e) return; - vsync

11

对于您的目的,我建议将其作为新对象构造函数的原型,并覆盖您想要更改的部分。由于JS中的循环引用问题,克隆可能会变得混乱,因此它可能不是您希望得到的快速且简单的解决方案。

function cloneEventObj(eventObj, overrideObj){

   if(!overrideObj){ overrideObj = {}; }

   function EventCloneFactory(overProps){
       for(var x in overProps){
           this[x] = overProps[x];
       }
    }

    EventCloneFactory.prototype = eventObj;

    return new EventCloneFactory(overrideObj);

}

//So add your override properties via an object
$el.click(function(e){
    var newEventObj = cloneEventObj(
        e,
        { target:document.body }
    );
    doSomething(newEventObj);
});

//or just stick 'em on manually after spitting the object out
/*...
var newEventObj = cloneEventObj(e);
newEventObj.target = document.body
...*/
在这种情况下,“克隆”的对象是新对象的原型对象。在检查原型对象之前,将首先检查“this.”属性,因此这些属性将被覆盖。或者,你可以在构建对象后附加属性。

1
在我多年的JS编程经验中,我从未考虑过或听说过有人使用这种令人惊叹的技术。它不仅简单,而且高效! - Thomas Eding
有人可以告诉我克隆事件何时会派上用场吗?我了解事件冒泡和捕获的用例,但我从未能想出何时需要克隆事件。 - CodeOwl
@CodeOwl 事件对象信息可能会触发其他形式的异步行为,您想能够将触发器排队,以便按顺序发生。在IE<=8中,事件对象只是全局窗口事件,这个事件会被反复覆盖。不确定旧引用是否指向旧的事件对象。我认为这取决于它是如何创建的。 - Erik Reppen
4
这段代码在Chrome浏览器中无法使用只读属性:function Event() {} Event.prototype = event; var ev = new Event(); console.log(ev.currentTarget); 会产生“TypeError: Illegal invocation”错误。必须直接在原始事件对象上访问这些属性。 - Jakub Vrána
1
是的。getter/setter 在这种方法中会产生一些问题。 - Erik Reppen
显示剩余6条评论

4

选项1:创建一个带有修改的新事件

您可以使用类似于Object.assign的方法,使用代理来构造一个新的事件,而不会修改原始事件

示例:

function EventModifier (evt, obj) {
  const proxy = new Proxy(evt, {
    get: (target, prop) => obj[prop] || target[prop]
  })
  return new evt.constructor(evt.type, proxy)
}

onclick = evt => {
  evt = new EventModifier(evt, { altKey: true })
  // Dispatch the new event on something else
  console.log('clicked with alt key:', evt.altKey) // always true
}

这种方式可以使用与原始事件相同的选项,包括冒泡、可取消、键修饰符等(但它不包括任何目标,因为您打算在其他地方分派修改后的事件)。

选项2:定义新属性

保留原始事件,但使用Object.defineProperty覆盖一个键,如果要添加多个,则可以使用defineProperties。

onclick = evt => {
  Object.defineProperty(evt, 'target', { value: document.querySelector('script') })
  console.log('target:', evt.target)
}


-2
受Kofifus答案的启发,我也这样做。
function cloneEvent(type, event) {
    var evt = new Event(type);
    return Object.setPrototypeOf(evt,event);
}

或者只是

function cloneEvent(event){
  return Object.create(event)
} 

它返回一个新对象,保护你不编辑原始对象,但允许你读取和编辑属性。这要归功于js原型链

最新的js引擎支持'structuredClone',可以为您完成此操作


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接