What did I just do? (trying to fire callbacks related to object methods)

Hello,
I will be very grateful if someone could explain to me why both examples do what they do.

First case.
My first approach, which didnt’t yield expexted results .

const Tetris = function(name) {
  this.name = name;
};
Tetris.prototype.moveDown = function() {
  console.log(this); // gives  {ArrowDown: ƒ} 
  console.log(this.name + " moved down!"); // gives 'undefined moved down!
};
const tetris = new Tetris('Square');

const keydownHandler = function() {
  let targetObject = tetris;
  const listenedKeys = {
    ArrowDown: targetObject.moveDown,
  };
  Object.keys(listenedKeys).forEach((name) => {if(event.key === name) { listenedKeys[name]() } });
};
window.addEventListener('keydown', keydownHandler);

However if I wrap the tetris instance like this:

const tetrisWrapped = (function() {
  let tetrisInstance = tetris;
  function moveDown() {
    tetrisInstance.moveDown()
  }
  return {
      moveDown: moveDown
  };
})();

and let targetObject in the keydownHandler be tetrisWrapped, then I get what I want: “this” is the Tetris constructor and its moveDown method knows what is doing.

I am glad that I found the way around, but I 'd prefer to know what I am doing. :slight_smile:
Thank you!

For one, you are not passing event into your event handler callback function. So the event parameter you are trying to access to compare the keyname doesn’t exist.

I definitely would not be satisfied with the state of whatever you’re trying to accomplish. I can’t quite understand why you have this setup this way.

In the first block of code, I see that listenedKeys is created as a new Object literal with the property ArrowDown assigned to the moveDown function.

Okay, I can see where the confusion lies. You now have a listenedKeys object defined as follows:

{
  ArrowDown: function() { 
    console.log(this);
    console.log(this.name);
  }
}

Notice, however, that the this of listenedKeys has no name property, but it certainly does have a this object with the ArrowDown function property as defined in the declaration:

const listenedKeys = {
  ArrowDown: targetObject.moveDown,
};

JavaScript treats functions as standalone when inserted outside of their native instance of this. It is contrary to what you might expect this to be mean based of classic object-oriented languages. Let’s not forget that JavaScript is as functional as it is object-oriented.

In your second block of code, you are creating a immediately invoke function expression wherein tetris is reassigned as tetrisInstance. Ok cool, so this is preserved in the reassignment of tetris to tetrisInstance.

The function moveDown of tetrisWrapped invokes the moveDown of the tetrisInstance which does not have a moveDown function of its down. Thankfully, it does have one on its prototype inherited from Tetris which was originally instantiated through tetris. It was then reassigned to tetrisInstance in the moveDown function of tetrisWrapped.

This was a mouthful to explain, and I think it would help to watch this video followed by a further explanation on prototypes to get a better grasp of what you are dealing with here.

2 Likes

The event parameter does exist. Just take a look on the console:

const keydownHandler = function() {
  console.log(event)
};
window.addEventListener('keydown', keydownHandler);

You can find a short explanation here:
“The handler function, by default, when executed is passed the event object (that was created when the event/action you are interested in happened) as an argument.”.

As for my satifaction for the “way around” , I was satisfied I found any. at all :slight_smile: I am aware that it’s probably not the best one. Maybe you will know any good resources on design patterns including adding event listeners and defining handlers,? I assume, that if you have different kind of browser events coming form user interaction (like in a tetris game I am building), together with events coming from the code, there must be a way of somehow grouping the callbacks together and then looping through them if necessary? Would be the pub/sub pattern suitable?
Thanks.

The event is passed into the function by default, but you still need to declare an event parameter name.

I’m getting this errror in the firefox console using the code you provided as it is.

ReferenceError: event is not defined
keydownHandler/< debugger eval code:15:1
forEach self-hosted:261:13
keydownHandler debugger eval code:15:3

IF you want to use the event in keydownHandler, declare that parameter. You can name the parameter whatever you want, because as you quoted, it is passed into the function by default from the function using it as a callback, but as a callback function it won’t have any reference to that event if you don’t do so. It appears that by default a callback is not designed to grab arguments[0] if a user does not properly build their functions.

const keydownHandler = function( event )

Absolutely, it’s always the first step to get something working. I just hope you don’t grow to be satisfied with that. I’m speaking from experience.

As a general principle on design patterns when you see a definition for a function you must still imitate that definitions design. You did so when you used the forEach() method, the order of the parameters matters, if you don’t declare which parameters you want you won’t have access to that data from the method. The same is true with a callback for the addEventListener. EventTarget: addEventListener() method - Web APIs | MDN

1 Like

As @sosmaniac-FCC explained, the listenedKeys is just storing the method in itself. I like to use string to store commands and bracket notation to access methods.

You could do this instead.

listenedKeys = {
  ArrowDown: "moveDown"
}

if(event.key === name) {
  targetObject[ listenedKeys[name] ]();
}
1 Like