JavaScript Class with same name for property and method - javascript

When researching a problem I ran into when trying to remove an event listener that uses bind() similar to the syntax in use by the class constructor below (I am not trying to remove this one, but it would not be successful if I tried), I found two sources that show a solution to that similar to that in use by the onDown method below (I do want this one removed once no longer needed) that creates a separte function that does the binding and the addEventListener attaches the function by its name:
class TestClass {
constructor(el) {
el.addEventListener('mousedown', function () { this.onDown(el.id) }.bind(this));
}
onDown(id) {
// Here a property named onUp is created:
this.onUp = this.onUp.bind(this, id);
document.addEventListener('mouseup', this.onUp);
}
// Here a method named onUp is defined in the class:
onUp(id) {
document.removeEventListener('mouseup', this.onUp);
console.log("Mouse down on " + id + " and then mouse up.");
}
}
const d1 = new TestClass(document.getElementById("div1"));
const d2 = new TestClass(document.getElementById("div2"));
<div id="div1">Div 1: Click me and let go!</div>
<div id="div2">Div 2: Click me and let go!</div>
This code works and achieves my desired results, but what I observed is this creates a both property and a method with the same name, named onUp, in the objects created. This can also be verified by looking at the properties of the objects created (see image below). This is not otherwise possible as confirmed by the error received when calling the method that uses the same name as a property here:
class Test {
sameName = "property";
sameName() { return "method"; }
}
const testObject = new Test();
console.log(testObject.sameName); // Here Console outputs the value of the sameName property
console.log(testObject.sameName()); // This fails calling the sameName() method
Can someone help explain to me how this is working in the first example where an object has a property and method both with the same name? Is this a problem?
Note, I have alternate ways of doing this too. I can otherwise simply give one of them a different name:
onDown(el) {
this._onUp = this.onUp.bind(this, el);
document.addEventListener('mouseup', this._onUp);
}
onUp(el) {
document.removeEventListener('mouseup', this._onUp);
console.log("Mouse down on " + el.id + " and then mouse up.");
}
Or, the { once: true } option works for the example, but not in the actual code I am writing that uses different events (this is just a simplified example):
onDown(el) {
document.addEventListener('mouseup', function () { this.onUp(el) }.bind(this), { once: true });
}
onUp(el) {
console.log("Mouse down on " + el.id + " and then mouse up.");
}
Adding an image that shows the output properties of one of the TestClass objects. It first lists the property values. Since my example only had 1 property, I added another (named anotherProperty) to show the collection of properties, for control purposes. And under Prototype, it lists all the methods in the object. Both sections have an entry named onUp.
And here is another image if the output of the other Test object I built expecting to fail. In here you can see it is built with the same design; it has a property named sameName (which can be referenced) and it has a method under Prototype also names sameName (which fails when calling).
This failure is as expected. But again, in my first example, there is both a property and a method, both with the same name, onUp, and the method does function.

A method is just a property with a function for its value.
A property is a named “thing” attached to an object.
When you create an instance of a class in JavaScript (i.e. call a function with the new keyword) it creates an object with a prototype which is a reference to the class.
When you try to access a property on an object, it first checks the object itself to see if it has such a property. If it can’t find it, it looks at the prototype and checks that object for a property with that name. Then it checks the prototype of that object and so on until it runs out of objects on the prototype chain.
This is how inheritance works in JavaScript.
———
So when you call new TestClass you create an object which has a prototype which has an onUp method.
When the mousedown event fires, you say:
this.onUp = this.onUp.bind(this, id);
Which:
Looks for onUp on the object and doesn’t find it
Looks up the prototype chain
Finds it on the call
Calls bind on it to create a new function
Copies that function to the onUp property of the object itself (creating it)
Now you have an onUp property on the object itself and on the object up the prototype chain.
This generally isn’t a problem but in your specific case, it creates an inefficiency.
The second time you trigger the mousedown event it finds onUp on the object itself, but still calls bind on it to create a new function (which calls the previous function which calls the previous function).
Every time there is another mousedown event you get an additional layer of binding.
You could avoid this by testing with the hasOwnProperty method (which will tell you if the value exists on the object itself and not up the prototype chain).

Related

JavaScript: How to use a method within a prototype? [duplicate]

This question already has answers here:
JavaScript click event listener on class
(8 answers)
Closed 6 years ago.
I am trying to modify a plug-in (Pikaday) and add my own little bit of functionality.
The plugin is built using the proto-type system, and I have added my funciton within it.
Pikaday.prototype = {
//OTHER PARTS OF PLUGIN
showYearPicker: function()
{
document.getElementsByClassName('pika-year-label').onclick=function(){
console.log("asfd");
}
},
//OTHER PARTS OF PLUGIN
}
return Pikaday;
What I am having trouble understanding is how to actually trigger this method. Clicking the specified element doesnt work.
Would anyone know how I can include this function as part of the plugin?
Full code (minus my addition) is here if anyone is interested.
Defining a method on the prototype of a function in simple terms means you've defined a method on an object that will be one link up the prototype chain from all instances of this function.
You can think of it as a common "parent" or "object-above" for every instance of that function.
Therefore, to use your method, you must instantiate the plugin and call your function on it.
function Pikaday() {}
Pikaday.prototype = {
showYearPicker: function() {
console.log('called');
}
}
let pikaday = new Pikaday();
pikaday.showYearPicker(); // <-- call the function on the prototype
What this will do is check if pikaday has showYearPicker defined on it, and since it doesn't, it will go up the proto chain and check if that object has it, which it does.
In other words, this is essentially what the JS engine does behind the scenes:
let pikaday = new Pikaday();
pikaday.__proto__.showYearPicker();
Note that proto here is just for demonstration, its only recently been standardized in ES6 for legacy purposes, but this behavior should be left to the JS engine (The spec calls this proto link [[Prototype]]).
If needed, the proper way to access the prototype is to use Object.getPrototypeOf (in +ES6 you could use the semantically-clearer Reflect.getPrototypeOf)
function Pikaday() {}
console.log(
Object.getPrototypeOf(new Pikaday()) === Pikaday.prototype
);
console.log(
Reflect.getPrototypeOf(new Pikaday()) === Pikaday.prototype
);
One problem with your code is that getElementsByClassName will return a NodeList (not an array) of nodes that have this class.
This means you must either loop over this list and attach a handler to each element,
let nodes = document.getElementsByClassName('pika-year-label');
Array.from(nodes) // convert NodeList to an array so we can use forEach
.forEach(node => {
// attach a handler on each node
node.onclick = function (){
console.log("asfd");
}
});
// Side Note: You can also use array spread to convert the node list to an array
// [...nodes].forEach( ... )
or alternatively attach a handler to a common parent for all the elements to delegate the behavior.
document.body.onclick = function(e) {
if (e.target.classList.contains('pika-year-label')) {
console.log("asfd");
}
}
Finally, if you only want to add this function to an existing function's prototype, one way to do it is to simply define it as a method on that prototype:
function Pikaday() {}
// define a method on the existing prototype
Pikaday.prototype.showYearPicker = function() {
console.log('called');
};
let pikaday = new Pikaday();
pikaday.showYearPicker(); // <-- call the function on the prototype

why is this private method in a constructor?

I'm a bit puzzled by this coding pattern I've run into even though I've been studying up on 'this.' The following (simplified) code shows the pattern:
var MyConstructor = function MyConstructor() {
this._handlers = {
action: this.handleAction.bind(this)
};
};
MyConstructor.prototype.start = function(someObj) {
this.someObj.on(’some event’, this._handlers.action); //<--here
}
MyConstructor.prototype.handleAction = function() {
//do stuff
}
module.exports = MyConstructor;
My question is, why is the private method in the constructor required? Is this pattern avoid some common problem? Could the line commented //<--here simply be:
this.someObj.on(’some event’, this.handleAction);
No, they are different. The difference is in context, which means the value of this within the function.
this.handleAction passes the function to on without any context. There is no value of this specified. The value will be determined when the function is executed. It is very likely that the value will not be the a MyConstructor object, so this.start, for instance, will not refer to the right object, or indeed perhaps any object at all.
The solution is to bind context. This sets the context forever, so this will always refer to the right value. You see this line of code:
action: this.handleAction.bind(this)
This means that, when the code later refers to this._handlers.action, it will be sending the function to on with the appropriate context, so this will always point to the correct value.
The difference between the following lines
this.someObj.on(’some event’, this.handleAction.bind(this));
this.someObj.on(’some event’, this.handleAction);
... is that the first handleAction will run with this being the instance of MyConstructor, while the second will run in whatever context the event handling mechanism decides. If it is something like this, it will run with this being the global object:
function on (a, callback) {
callback(); // callback is run with this as the global object
// UNLESS it was bound to something else
}
The 'private' _handlers property is just an object that holds references to callbacks bound to the instance. If you were to call bind twice, two functions would be created. The _handlers property makes it so that a single bound function is created which can be used as a handler for any number of events.

Javascript OOP: binding method to event handler [duplicate]

This question already has answers here:
How does the "this" keyword work, and when should it be used?
(22 answers)
Closed 9 years ago.
I'm new to JS and I understand functions are the way to declare "class like" structures.
I'm trying to do something like this:
function Game() {
// Class stuff
this.handleClick = function(e) {
alert(e);
}
// Bind event to method previously defined
$('#board').bind('click', function(e) {
this.handleClick(e); // <--- THIS IS THE PROBLEMATIC LINE
});
}
Now, if in the "problematic line" I write:
handleClick(e)
I get Uncaught ReferenceError: handleClick is not defined.
Instead, if I write:
this.handleClick(e);
I get Uncaught TypeError: Object #<HTMLCanvasElement> has no method 'handleClick'
But then, if I do:
function Game() {
// Class stuff
this.handleClick = function(e) {
alert(e);
}
var game = this; // <--- I ASSIGN this TO board
// Bind event to method previously defined
$('#board').bind('click', function(e) {
game.handleClick(e); // <--- THIS WORKS!! WHY????
});
}
It works!?!?!
My questions are:
Why does this happen this way? I know this can be problematic, but why assigning it to a variable changes it like that?
Am I doing this wrong? Is there a better way to achieve something like this in a more elegant way?
You found the one hole in Javascript's implementation of closures. (If you don't know what closures are, don't worry about it.)
this in Javascript references the object on which the function was called. So if you have an object with a property that happens to be assigned the value of a function, and you call that property, the this pointer references the object. It does not reference whatever this was where the function was created, nor does it reference the function itself.
var object = {};
object.myfunc = function() { console.log(this); } //<-- this will refer to object
object.myfunc();
In the case of an event handler, the handler function (in pure Javascript, ignoring jQuery) gets assigned to the DOM element, so it gets called from that element and hence this points to the DOM element.
Using a local variable, like game in your example above, means that the function you created (a "closure") will "close" around that variable, capturing the values of any variable in scope at the place the function was defined. Any variable except this. So your work-around is a very common way to handle this.
Another common way to handle this would be to use a library like Underscore.js which has a function called bind that does some higher-order-function magic to make sure this always points to what you want.
$('board').bind('click', _.bind(function(e) {
this.handleClick(e);
}, this));
For this reason, Underscore.js is one of my favorite Javascript libraries (after jQuery), and I use it almost exclusively for the bind function.

How to efficiently register a DOM event with a specific JavaScript object instance

In my web application I have a custom object which I've defined with an object function constructor and applied various shared properties through the constructors prototype.
MyObject = function (Name) {
this.Name = Name;
this.ListItem1 = document.createElement('li');
this.ListItem1.onclick = this.SetActive;
this.ListItem2 = document.createElement('li');
this.ListItem2.onclick = this.SetActive;
}
MyObject.prototype.SetActive = function () {
alert('SetActive: ' + this.Name);
}
Now this is a simplified example, my actual object has many more DOM elements attached to it, and each of those DOM elements have many different event listeners. My object also has many other properties and methods as well. I could also potentially have thousands of instances of this object, so code efficiency is important.
My issue right now is that when a DOM event is triggered, the event handler's 'this' property is set to the actual DOM element, not my object instance.
For example:
var HelloObj = new MyObject('Hello');
HelloObj.SetActive();
//This alerts 'SetActive: Hello'
HelloObj.ListItem1.click();
//This alerts 'SetActive: undefined' because 'this' in SetActive
//becomes ListItem1 and obviously ListItem1.Name is undefined
So how can I set the DOM element's event handlers to a function pointer (not a new function instance for each event handler which would be inefficient when there's a large number of object instances) but still retain the context of the object instance itself in regards to 'this'?
Try out bind() like this:
this.ListItem1.onclick = this.SetActive.bind(this);
//and so on
The solution I've come up with and am using for now is to attach a reference to the object instance to the DOM element and then using a wrapper function for the event handlers to call the desired function through that reference:
MyObject = function (Name) {
this.Name = Name;
this.ListItem1 = document.createElement('li');
this.ListItem1.Context = this;
this.ListItem1.onclick = this.SetActiveHandler;
this.ListItem2 = document.createElement('li');
this.ListItem2.Context = this;
this.ListItem2.onclick = this.SetActiveHandler;
}
MyObject.prototype.SetActiveHandler = function () {
this.Context.SetActive();
}
MyObject.prototype.SetActive = function () {
alert('SetActive: ' + this.Name);
}
This solves all of my problems, although I'm not completely satisfied as there are a few pitfalls. Visually it's just not as pretty and is more convoluted. Programmatically it creates a circular reference which I know is generally frowned upon, although in my situation I don't think it should cause any issues as I'm only worried about modern browsers, and I believe the circular reference is fully contained such that if all of the DOM elements have been removed from the document and I delete my reference to the instance of MyObject itself everything should be fully garbage collected (some feedback on this would be greatly appreciated). And I know it's also considered bad practice to attach custom properties to a DOM element, although I don't know if that's still an issue with modern browsers (again, some feedback would be great). It also breaks the object-oriented flow a little bit having to go though a second function, I would have liked to attach the event handler directly to SetActive().
Does anybody have any other solutions or could shed some light on whether or not I'm actually creating more problems than I'm solving?

How do I reference an object method in javascript from outside the object (through event)?

I am creating a fairly complex object and want to be able to manipulate the object from the outside. I want to reference an privileged method from a click event
function myObject(){
//How do I reference this function?
this.doSomething = function (action){
var doing = action;
}
}
I understand that I could reference the method if I create the object inside a variable like:
var obj = new myObject();
obj.doSomething('hit keyboard');
But the links I am creating to trigger this event are being created by the object, but placed outside the object, so they will not know the container variable to reference it.
i.e.
Do Something
doesn't work because of course the doSomething method is part of the object, not a global function. I could rewrite the code to make it global, but would rather avoid doing that.
If this question doesn't make sense I would be happy to make it clearer. Thanks in advance.
Although it will sit in the global scope, and generally not recommended, you could do this (if you just need to get it working):
function myObject(){
//How do I reference this function?
window.doSomething = function (action){ // USE window instead of this !!
var doing = action;
}
}
By using window instead of this, it can be called in your handler.
IF the object instance is already created, then you can say:
Do Something
Otherwise,
Do Something
You need the object to be in the global scope. Try this:
window.myObject = {
doSomething: function(action) {
alert("Done!");
}
};
and then you make a click event like
Do Something
Example
To define the problem, you're in a particular object's click handler and you want to call a method on another object in your code that you can't get to directly from the object you're in. You really only have these options and you can just pick the one that seems best or seems the least objectionable:
You can make the object that you want to call a method on be in the global scope.
You can make the object that you want to call a method on be findable in the global scope (either in a parent hierarchy or with some sort of global find). It can be done this way without necessarily adding any more top level objects to the global scope.
You can set the object that you want to call a method as instance data on all objects that will have the click handler so that you can just reference the global object from the click handler object's instance data.
You can declare the click handler to have a parameter that contains your object by using function closures and avoid all global data.
The problem is that there's a disconnect between the instance of an object and the element on the page whose event should trigger the object's function. There are various ways to handle that without exposing anything globally. One way, for example, would be to tie the two via an object's property:
Give the element an ID:
<a id="myLink">Do Something</a>
Add element property to object:
function myObject(element){
this.element = element;
if (this.element) {
this.element.onclick = this.doSomething;
}
this.doSomething = function () {
// do stuffs.
}
}
Instantiate object:
var obj = new myObject(document.getElementById('myLink'));
Or simplify the object definition a bit more:
function myObject(element){
this.element = element;
if (this.element) {
this.element.onclick = function () {
// do stuffs.
}
}
}

Categories

Resources