The question
In Javascript, how can an event handler function refer to members of its parent? i.e. can you define an event handler function part of a larger object and have that function "know about" its parent?
(Note that someone else posted a nearly identical question Accessing variables of parent function in Javascript event handlers . It hadn't been answered. Hence the repost )
Erroneous Presuppositions
I had thought that at "function definition" time you could capture a copy of "this" for later re-use, (e.g. copy to "self"). Evidently I was wrong: after I bind the function to the event ("click()" below), "self" subsequently refers to the html anchor tag ('');
The general context :
trying to use encapsuation/object-orientedness for code-re-use in javascript.
Example
Here's a simple example (cribbed from elsewhere and modified).
The function succeeds when called during page load, but fails when the user preses "click"
MY LINK
<script type="text/javascript">
var Construct = function() {
var self = this;
this.attr1 = 3;
this.attr2 = 2;
this.childObj = {
method1: function () {
// this function fails if called from an event handler
// edited this function to "do something", i.e. provide a visual cue upon execute
var foo = self.attr1 * self.attr2;
alert ('value is ' + foo);
return foo;
}
}
}
var obj = new Construct();
// this call succeeds
alert (obj.childObj.method1());
//this call fails as soon as the event handler refers to "self"
$("#myLink").click(obj.childObj.method1);
</script>
</body>
</html>
Update/Edit
-Updated the example to give a 'visual cue' when it runs
-added this section.
My error. As pointed out below, the example works fine. My original, non-working code used this:
this.self = this
instead of
var self = this
I didn't realize the distinction (in Java they would be identical) and missed the fact that my example actually worked, (whereas my code failed).
Your code works fine. self refers to the object as it is supposed to. That's how the lexical scoping of javascript is defined.
The problem is your handler does nothing. method1 simply returns 6 but you never tell it do anything with that value. If you want to prove it to yourself, the line before the return, add an alert: alert(self.attr1 * self.attr2);
Working Example
Related
Im doing a project right now where certain elements of the page change depending on the mouse positioning and made some code basing myself off an explanation I saw in Mozilla. I understand most of it, but there is still one part that is bothering me, which is the function parameter. Throughout my learning of basic Javascript, I have never understood parameters despite the countless explanations. What does a parameter do? How does the page know its purpose? To give an example here is the code:
<!doctype html>
<html>
<head>
<title>Change</title>
</head>
<body>
<p id="hurb"></p>
<script>
document.addEventListener('mousemove', movee);
function movee(a) {
var eub = a.clientX;
document.getElementById("hurb").innerHTML = eub;
}
</script>
</body>
</html>
What does a do here? How does the page know what it does?
Can someone please explain this in a way that can be understandable for a beginner? All the other explanations in pages dont really help me out.
Thanks!
The parameter, as its name suggests, is something upon which usually the function's return value depends. Hence, your function can have a parameter that is not used throughout the function. Here is an example:
function foo() {
console.log("foo");
}
function bar(par1, par2, par3) {
console.log("bar");
}
foo(); // foo
bar(); // bar
bar(45,100); // bar
Note that however you call bar() it logs "bar", since the parameter is never used inside the function and hence never contributes to the return value of the function.
In your case, the function is an event handler function, i.e. is called when some event (mousemove in your case) is fired. When an event is fired, the browser passes an Event object (particularly a MouseEvent object in your case) to the event handler function. This event object is a typical JS object, with properties such as clientX, clientY, pageX, pageY, etc.... By defining your function with a parameter, that parameter is going to take the value of the Event object. To access the Event object's properties, you do not need to define a parameter for your function. Hence, this function:
function movee(a) {
var eub = a.clientX;
document.getElementById("hurb").innerHTML = eub;
}
and this one:
function movee() {
var a = event;
var eub = a.clientX;
document.getElementById("hurb").innerHTML = eub;
}
are typical of each other.
How does the page know its purpose?
The page doesn't. All the page does is that it executes your function. If it encounters the parameter, it uses its value. Otherwise, it just continues execution.
Here is the app I'm referring to:
I am trying to fundamentally understand the bind method in Javascript.
My understanding when I play around with it in the console is that bind returns a copy of the function, with "this" bound to whatever you pass into bind.
function logThis(){
console.log(this)
}
logThis.bind({today: 'Tuesday'})
//Will return a copy of the logThis function, with 'this' set to the
{today:'Tuesday'} object. The code does not run right away though.
var explicitlyLogThis = logThis.bind({today: 'Tuesday'});
explicitlyLogThis(); //This will run the code and display the {today: 'Tuesday'} object to the console.
This is my understanding so far. I understand that to actually run this new function that has 'this' explicitly bound using the bind method, you need to set it to a variable and then run it.
I see a contradiction when I look at the app in the above link. If you look at the bindEvents method on line 56, we have .on('keyup', this.create.bind(this)). I understand that we have to set 'this' to App when we run the create method because jQuery defaults to setting 'this' to the jQuery object itself. So this line is actually the same as: $('#new-todo').on('keyup', App.create.bind(App)).
That isn't where my confusion is. My question is:
How exactly are these copies of the functions with 'this' set to App actually being called? The app does not set them to a variable and then call that variable the way I had to when I was working in the console.
It just invokes the bound functions directly as soon as an event occurs on one of the jQuery elements. But I thought writing it this way would just return a copy of the function, and not run the function itself, if I am basing my assumptions on what I have figured out in the code I wrote above. I thought in order to invoke the function immediately, you would need to use call or apply.
I also realize that the app runs the bindEvents method when it starts (see line 46). So I understand that when you start the app, copies of the various functions are created with the correct 'this' bound to the functions. But...when/how do they actually get invoked without assigning them to variables? How are these copies accessed?
I think I have a flawed understanding of the bind method, so I would love some help. Thanks!
It sounds like you understand bind well enough. Perhaps there is some confusion with passing anonymous functions. As you know calling bind returns a new function and this can optionally be stored as a variable or passed as a function argument.
In the example below btn1 accepts a bound function as you've seen. This could also be written in a more long hand fashion with btn2. They're identical. btn3 doesn't receive a bound function, when its clicked its context is the button element, this looses all visibility of MagicalApp fucntions.
<button id="example1">button one bound</button>
<button id="example2">button one bound</button>
<button id="example3">button two unbound</button>
<script>
class MagicalApp {
add() {
console.log('this could do addition');
}
}
const app = new MagicalApp();
function contextOfEvent(event) {
console.log('contextSensitive', this.add)
}
const btn1 = document.querySelector("#example1");
btn1.addEventListener('click', contextOfEvent.bind(app));
const btn2 = document.querySelector("#example2");
const btn2ClickHandler = contextOfEvent.bind(app)
btn2.addEventListener('click', btn2ClickHandler);
const btn3 = document.querySelector("#example3");
btn3.addEventListener('click', contextOfEvent);
</script>
so I'm having a problem that seems to defy everything I know about how scope is handled in JavaScript with anonymous functions - but it could be something else I'm not thinking about.
I have a JavaScript object, called Element, with a constructor similar to this:
function Element(boxElement) {
var self = this;
// Set jquery instance variables
self.pageElement = null;
self.boxElement = boxElement;
... blah blah blah
// Implement triggers to empty functions
self.onElementClicked = function () {};
// Bind listeners
self._bind_listeners();
}
The bind_listeners method is defined as such
Element.prototype._bind_listeners = function() {
var self = this;
self.boxElement.on('click', function (e) {
// Don't handle if handled already
if (e.isDefaultPrevented()) return;
console.log("Got past the return");
self.onElementClicked();
});
};
And there's also a method to set the callback method onElementClicked:
Element.prototype.on_element_click = function(callback) {
var self = this;
self.onElementClicked = callback;
};
The problem I am encountering is that if I set my callback using the on_element_click method, my method doesn't see the current instance - it sees what the instance would look like just after construction.
More specifically to my situation, there's an instance variable called boxElement that refers to a JQuery element - and in Chrome's console I can see that the instance (self) still does refer to the correct element on the page, but the onElementClicked instance variable (and others) do not seem to be set from within the listener.
Feel free to revise my explanation or ask for clarification.
From the implementer perspective:
If I do this:
// Set default listener for element click
formElement.on_element_click(function () {
console.log("Hello");
});
The listener never says Hello because onElementClicked doesn't appear to be set.
However, if I instead do this:
formElement.boxElement.click(function () {
console.log("Hello");
});
It successfully says "Hello" and makes me confused.
I found the solution to my specific problem, which is a good example of how an error like this can occur. (offtopic: please feel free to add answers for other ways to produce this error - it is a very non-intuitive problem and will always be caused by an external factor)
It turns out the class I was testing with is a class that extends my Element class - BUT, it does so improperly / VERY VERY badly!
As embarrassing as it is to post this, here's the original constructor of my "subclass" (quotes for reasons soon apparent):
function StrikeoutFormElement (formElement) {
var self = this;
// Set reference to form element
self.fe = formElement;
$.extend(self, self.fe);
// Override methods
self.on_reposition(function () {
self._on_reposition();
});
}
I used JQuery's object extending function and a hacky workaround to override something. I have learned the hard way to NEVER use JQuery's extend for OOP, as it is only intended for data manipulation rather than as a language tool.
The new constructor looks like this:
function StrikeoutFormElement (elem) {
var self = this;
}
// Extend the FormElement prototype
StrikeoutFormElement.prototype = Object.create(Element.prototype);
StrikeoutFormElement.prototype.constructor = Element;
This is a method described in an MDN article somewhere. I'll post the source when I find it if someone doesn't beat me to it.
Shoutout to anyone who looked at this obscure problem and attempted to figure it out!
I've been reading just about every article I can get my hands on about JavaScript scope to better understand it. I'd like to perfectly understand it by the end. I'm currently reading this article: http://www.digital-web.com/articles/scope_in_javascript/ and I've just finished reading the "Complications" section (a little more than halfway down) and thought it was very helpful, but not quite clear enough.
It uses the following code and considers the onclick behavior of the_button:
function BigComputer(answer) {
this.the_answer = answer;
this.ask_question = function () {
alert(this.the_answer);
}
}
function addhandler() {
var deep_thought = new BigComputer(42),
the_button = document.getElementById('thebutton');
the_button.onclick = deep_thought.ask_question;
}
window.onload = addhandler;
The article states ... an event handler[,] runs in a different context than when it’s executed as an object method. So, if I'm to understand correctly, then the call to the ask_question method in context of the script's object method is deep_thought.ask_question, making this deep_thought. But when an event in the DOM is triggered, then the call chain changes to DOMelement.eventHandler.deep_thought.ask_question making this DOMelement?
That is correct! 'this' in event handlers is the element you bound to. In this case it would be the_button. The alert would be 'undefined' as the_button has no the_answer property.
You can see an example at: http://jsfiddle.net/zG7KR/
See what this outputs:
this.ask_question = function () {
alert(this.the_answer);
};
I have a difficulty in understanding, how my current JavaScript code works. I've managed to solve a problem in accessing private object method from event handler closure, but I'd like to know why does it work so.
The code utilizes the well-known module/plugin metaphor:
(function(module, $, undefined)
{
function myPrivateCode(e){ /*...*/ }
module.myPublicCode = function(e) { /*...*/ }
module.init = function()
{
var that = this;
$('.clickable').click(function(e)
{
if($(e.target).hasClass('classX'))
{
that.myPublicCode(e.target); // requires 'that' to work
}
else
{
// that.
myPrivateCode(e.target); // will fail if 'that' uncommented
}
});
}
}(window.module = window.module || {}, jQuery ));
In the code I set a click handler which invokes either public or private method. It's perfectly conceivable that we need to pass an object reference into the event handler closure, which is done by that local variable. What is strange to me is that myPrivateCode does neither require that as a refernce, nor fails due to its "privacy". This makes me think that myPrivateCode accesses not the appropriate object, and works somehow differently to expected way. Could someone explain what happens? Certainly I'm missing something.
Both that and myPrivateCode are available to your event handler through a closure. In short, what's going on is that every variable and function you declare inside of another function has access to the outer scope.
myPublicCode, on the other hand, is not available through closures, because it's being assigned to your module object specifically. So the only way to call it is by using module.myPublicCode() (or that.myPublicCode() as you did – but you don't actually need that there, since module is also available).
Your call to myPrivateCode(e.target); is running in the context of the anonymous function that you pass as a handler to the click function.
For more information, read up on closures.
For a simpler example, try out this code:
var foo = function () {
var a = 1;
return function (b) {
return a+b;
}
};
var bar = foo();
bar(1); // 2
bar(1) will always always gives 2, because a = 1 was in scope when the function was created. In your case, a is your that and your handler is the closed function.
http://jsfiddle.net/Fh8d3/