【问题标题】:Illegal invocation on addEventListener.call(ob,...) on custom class在自定义类上非法调用 addEventListener.call(ob,...)
【发布时间】:2020-08-25 13:46:27
【问题描述】:

我在addEventListener.call(ob) 上收到非法调用错误。这是我的示例代码:

function MyClass(name) {
  var target = document.createTextNode(null);

  this.addEventListener = target.addEventListener.bind(target);
  this.removeEventListener = target.removeEventListener.bind(target);
  this.dispatchEvent = target.dispatchEvent.bind(target);

  this.MyName = name;

  this.ModifyName = function(newName) {
    this.MyName = newName;
    
    this.dispatchEvent(new Event("Change"));
  };
}
MyClass.prototype = EventTarget.prototype;


function changeHandler() {
  console.log(`From changeHandler`);
}
function changeHandler2() {
  console.log(`From changeHandler2`);
}

function test() {
  var ob = new MyClass("OrgName");
  ob.addEventListener("Change", changeHandler);

  addEventListener.call(ob, "Change", function() { changeHandler() }); // <-- Illegal invocation

  console.log(ob.MyName);

  ob.ModifyName("UpdatedName");

  console.log(ob.MyName);
}

test();

更新

我实际上已经为 XmlHttpRequest 创建了代理类,它拦截所有的 ajax 请求/响应。它工作正常,直到 angular 开始使用 addEventListener.call(xhr, "readystatechange", callback)。在这里,xhr 是我的代理类的对象。我对角码没有任何控制权。我只能修改我的代理类。我可以通过上面的示例代码重现“非法调用”错误。所以上面的示例代码就像我的场景的模拟器。

有什么建议吗?

【问题讨论】:

  • 您不能将事件侦听器添加到文本节点。将文本包裹在一个跨度或其他东西中。
  • 你也不能在课堂上调用它。 addEventListener 属于 DOM 元素。

标签: javascript events dom-events addeventlistener


【解决方案1】:

以上的cmets只是猜测代码失败的原因。

尽管幸运的是所有其他代码气味都不会导致错误,但 test() 中最大的错误(在代码示例的第 30 行)是调用 addEventListener.call(ob, "Change", function() { changeHandler() });

调用是非法的,因为它会欺骗绑定的上下文。它失败是因为globalwindow 对象不等于ob。如果调用例如 ob.addEventListener.call(ob, "Change", changeHandler2); 一切都很好,因为上下文仍然以相同的引用为目标。

function MyClass(name) {
  var target = document.createTextNode(null);

  this.addEventListener = target.addEventListener.bind(target);
  this.removeEventListener = target.removeEventListener.bind(target);
  this.dispatchEvent = target.dispatchEvent.bind(target);

  this.MyName = name;

  this.ModifyName = function(newName) {
    this.MyName = newName;
    
    this.dispatchEvent(new Event("Change"));
  };
}
MyClass.prototype = EventTarget.prototype;


function changeHandler() {
  console.log(`From changeHandler`);
}
function changeHandler2() {
  console.log(`From changeHandler2`);
}

function test() {
  var ob = new MyClass("OrgName");
  ob.addEventListener("Change", changeHandler);
  
  // allowed because the context still does target the same reference.
  ob.addEventListener.call(ob, "Change", changeHandler2);

  // - not allowed because it tries to cheat about the bound context, ...
  //   ...it fails because `global` or `window` object does not equal `ob`.
  //
  // //addEventListener.call(ob, "Change", function() { changeHandler() });

  console.log(ob.MyName);

  ob.ModifyName("UpdatedName");

  console.log(ob.MyName);
}

test();
.as-console-wrapper { min-height: 100%!important; top: 0; }

因为 OP 的示例无论如何已经使用了 EventTarget(第 16 行确实声明了 MyClass.prototype = EventTarget.prototype;),所以可以切换到 class 语法并以 (c) 更精简的方式实现 MyClass ...

class MyClass extends EventTarget {
  constructor(myName) {
    super();

    // make it cheat proof, preserve the original `this` context.
    var eventTarget = this;

    this.name = myName;

    this.modifyName = function(newName) {

      // using the outer `eventTarget` prevents a `this` context change
      // from outside via e.g. `ob.modifyName.call(obj_2, "UpdatedName_2")`
      eventTarget.name = newName;

      eventTarget.dispatchEvent(new Event("namechange", {
        srcElement: eventTarget,
        currentTarget: eventTarget,
        target: eventTarget
      }));
    };
  }
}


function changeHandler(evt) {
  console.log('From changeHandler');
  // console.log('From changeHandler :: evt :', evt);
}
function changeHandler_2(evt) {
  console.log('From changeHandler_2');
  // console.log('From changeHandler_2 :: evt :', evt);
}

function test() {

  var ob = new MyClass("OrgName");
  ob.addEventListener("namechange", changeHandler);

  var obj_2 = { name: "OrgName_2" };
  obj_2.dispatchEvent = ob.dispatchEvent.bind(obj_2);

  // allowed because the context still does target the same reference.
  ob.addEventListener.call(ob, "namechange", changeHandler_2);

  // // throws invocation error cause it tries to cheat about the context.
  // ob.addEventListener.call(obj_2, "namechange", changeHandler_2);

  console.log(ob.name);
  console.log(obj_2.name);

  ob.modifyName("UpdatedName");
  ob.modifyName.call(obj_2, "UpdatedName_2");

  console.log(ob.name);
  console.log(obj_2.name);
}

test();
.as-console-wrapper { min-height: 100%!important; top: 0; }

【讨论】:

  • 感谢@peter-seliger 的回复。 1. 'class' 不能在旧版浏览器上运行,对吧? 2. 我实际上已经为 XmlHttpRequest 创建了代理类,它拦截了所有的 ajax 请求/响应。它工作正常,直到 angular 开始使用 addEventListener.call(xhr, "readystatechange", callback)。在这里,xhr 是我的代理类的对象。我对角码没有任何控制权。我只能修改我的代理类。
  • @Hitesh ...也许您可以通过将最后提到的用例简化为另一个方便的代码示例来将此新信息添加到您的原始问题中。
  • @Hitesh ... 提供代理 xhr 实现的最重要部分仍然有助于理解代码实际失败的位置和方式/原因。
  • addEventListener.call(xhr, "readystatechange", callback) 订阅 readystatechange 事件时失败。这里,xhr 是代理对象。
  • 对我来说,这听起来像是想要拦截任何 xhr 方法的数据流。代理并不是完成此类任务的唯一方法。请打开另一个 Q 并准确提供所有必要的代码和信息。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多