0

I am getting Illegal Invocation error on addEventListener.call(ob). Here is my sample code:

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();

UPDATE

I actually have created proxy class for XmlHttpRequest, that intercept all the ajax request/response. It works fine until angular started using addEventListener.call(xhr, "readystatechange", callback). Here, xhr is the object of my proxy class. I don't have any control on angular code. I can modify only my proxy class. I could reproduce the 'Illegal Invocation' error by above sample code. So the above sample code is like simulator of my scenario.

Any suggest?

Hitesh
  • 1,067
  • 1
  • 10
  • 27

1 Answers1

1

The above comments are merely guessing the reason of the code failure.

Despite all the other code smells that luckily do not contribute to throwing errors, the biggest mistake within test() (at line 30 of the code example) is to call addEventListener.call(ob, "Change", function() { changeHandler() });.

The invocation is illegal because it is going to cheat about the bound context. It fails because the global or window object does not equal ob. If one invokes e.g ob.addEventListener.call(ob, "Change", changeHandler2); everything is perfectly fine because the context still does target the same reference.

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; }

Because the OP's example anyhow already makes use of EventTarget (line 16 does state MyClass.prototype = EventTarget.prototype;), on could switch to class syntax and implement MyClass in a (c)leaner way ...

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
  • 11,747
  • 3
  • 28
  • 37
  • Thanks @peter-seliger for the response. 1. 'class' will not work with older browser, right? 2. I actually have created proxy class for XmlHttpRequest, that intercept all the ajax request/response. It works fine until angular started using addEventListener.call(xhr, "readystatechange", callback). Here, xhr is the object of my proxy class. I don't have any control on angular code. I can modify only my proxy class. – Hitesh Aug 26 '20 at 04:39
  • @Hitesh ... maybe you add this new information to your original question by boiling down the last mentioned use case to another handy code example. – Peter Seliger Aug 26 '20 at 12:05
  • @Hitesh ... providing the most important parts of the proxified xhr implementation will still contribute a lot to the understanding of where and how/why the code actually fails. – Peter Seliger Aug 26 '20 at 12:27
  • it is failing when readystatechange event is subscribed by addEventListener.call(xhr, "readystatechange", callback). Here, xhr is proxy object. – Hitesh Aug 26 '20 at 12:59
  • To me it sounds like one wants to intercept the data-flow of any xhr method. A proxy is not the only way of achieving such a task. Please open another Q and provide all the necessary code and information exactly there. – Peter Seliger Aug 26 '20 at 15:11
  • Created another question - https://stackoverflow.com/questions/63601256/illegal-invocation-on-addeventlistener-callxhr-readystatechange-callback – Hitesh Aug 26 '20 at 15:52