I want to make a class in node that runs a set of stages that can be easily added or removed. My current code looks like this:
function MyClass(){
this._stages = [this.function1, this.function2];
this._info = {'a':'special object info'};
};
MyClass.prototype.run = function(){
for(var i = 0; i<this._stages.length; i++){
this._stages[i]();
}
};
MyClass.prototype.function1 = function(){
this.subfunction1();
};
MyClass.prototype.subfunction1 = function(){};
Unfortunately, it seems like putting the function inside the Array messes up their 'parent', and I get an error saying that
TypeError: Object function (query) {
[...long list of elements]
} has no method 'subfunction1'
at Array.MyClass.function1...
Is there a way to accomplish this by-stage execution without having this happen?
Thanks!
When you put it in the array, it loses its association with this. So, when you then call those functions, they won't have the right this value. You can fix it with .bind():
function MyClass(){
this._stages = [this.function1.bind(this), this.function2.bind(this)];
this._info = {'a':'special object info'};
};
A different way to fix it would be to reattach this when you call the functions by using .call() to specifically set the this pointer like this:
MyClass.prototype.run = function(){
for(var i = 0; i<this._stages.length; i++){
this._stages[i].call(this);
}
};
As an explanation, when you do this:
var x = this.function1;
x();
then, all that's in the x variable is a pointer to the function1 function. If you call x(), it will be called without any object reference so this in the function will be either the global object or undefined if running in strict mode. It will not be the desired object.
.bind() creates a stub function that stores the this value and then calls the original function with the right object reference such that the this value is set properly.
If you want a review of how this is set, you can see this answer: When you pass 'this' as an argument
Related
Language: Javascript
Operating System: Ubuntu 14.04
Browser: Tested in Chromium, Chrome, Firefox.
Background
I'm making a HTML5 Canvas game that has various collectable items. The superclass for these items is Collectable, which has a render() function:
Collectable.prototype.render = function(){
// renders the collectables
}
There are different subclasses of Collectable, all of which delegate render() to the superclass. For example:
var Heart = function(x, y) {
Collectable.call(this, x, y);
}
Heart.prototype = Object.create(Collectable.prototype);
I instantiate my collectables by first creating an array containing all of my collectables:
var collectables = [];
var numOfEach = 5;
// IIFE so as to not pollute global scope
(function() {
for (var i = 0; i < numOfEach; i++) {
// TODO: make this cleaner
var heart = new Heart(100, 200);
collectables.push(heart);
var gemBlue = new GemBlue(100, 200);
collectables.push(gemBlue);
var gemOrange = new GemOrange(100, 200);
collectables.push(gemOrange);
}
})();
Then in my game loop file, I have a renderEntities() function that repeatedly calls individual render methods on my objects:
function renderEntities() {
// render my other objects
...
// render collectables
collectables.forEach(function() {
// test what 'this' is bound to
console.log(this);
// call render method
this.render();
});
}
...however in this case this is bound to window and therefore this.render is not a function.
Question
How do I bind this to each individual element of my collectables array?
My research
I've read the documentation for the bind function, but I'm afraid I can't work out how this would be applied with forEach() and my anonymous function.
I then found this existing question which explained how forEach takes an optional parameter to set this. After reading the documentation on forEach I tried the following with unsuccessful results:
collectables.forEach(function() {
// test what 'this' is bound to
console.log(this);
// call render method
this.render();
}, this);
...which obviously had the same effect of setting this to the global object.
collectables.forEach(function() {
// test what 'this' is bound to
console.log(this);
// call render method
this.render();
}, collectables);
...this got me a little closer, as this is bound to the collectables array as a whole, but obviously this.render is still not a function. I can't see what argument I can use here in forEach to bind this to each element of the array.
I have a feeling that after typing all this out the answer will be very simple. I'm relatively new to the concept of this so go easy on me!
I haven't found another question that answers this for me, or at least one that I'm able to understand given my level of knowledge. For example, I have considered the following questions:
Calling a class function in forEach: how Javascript handles “this” keyword.
The invocation context (this) of the forEach function call
Passing scope to forEach
If I understand correctly, you are looking to get the object associated with each collectable item. In this case, simply pass an argument for the forEach loop. In this case, I've called that argument theObj but perhaps theCollectable or even just c would be more accurate.
collectables.forEach(function(theObj) {
// test what 'this' is bound to
console.log(theObj);
// call render method
theObj.render();
});
Update: the this you are passing in the forEach call is passing the current value of this to the loop. At that point, this is executed within the scope of window, and that's why this is window. You do not need to pass this if you do not wish to refer to the window, and since window is global you really have no need to pass this at all, so I've removed it from the suggested answer.
Update: the MDN is a great resource for JavaScript documentation: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
The .forEach() facility passes three arguments to your callback function:
The current element of the array;
The index into the array;
The array itself.
Thus:
collectibles.forEach(function(collectible) {
// the parameter "collectible" will be the
// current array element
});
There's really no need to bind this, but if you absolutely wanted to you could:
collectibles.forEach(function callback(collectible) {
if (this !== collectible)
callback.call(collectible, collectible);
else {
// body of function
}
});
I have this code:
PageList: function(url, index, classes){
this.url = url;
this.index = index;
...
};
PageList.prototype.toHTML = function(){
var div = $('<div class="container"></div>');
var p = $('<p></p>');
var link = $('<a></a>');
$.each(this.elements, function(index, array_value){
console.log(this.url);
...
}
}
And it worked as expected.
The problem was that console.log(this.url) was printing undefined, so I reworked the code to look like this:
PageList.prototype.toHTML = function(){
var div = $('<div class="container"></div>');
var p = $('<p></p>');
var link = $('<a></a>');
var instance = this;
$.each(this.elements, function(index, array_value){
console.log(instance.url);
}
}
I know that the problem was on the closure not taking this as the value of the instance, but as far as i know a reference to this inside a function that doesn't have an instance bound to it must refer to the window object, instead of undefined, at least that's the case on many of the browsers out there.
So what exactly is going on on my code.
Note: I'm using jQuery and this.elements is already defined.
Edit: Now im figuring out that $.each is a non-instance function, so my callback is being called from $.each but it must be window the reference to this, still thinking about it.
According to the jQuery docs for $.each:
The value [of the current element] can also be accessed through the this keyword...
In JavaScript, when you hand off a callback function to a higher-order function (in this case, $.each), the higher-order function can decide what the value of this will be when the callback runs. There is no way for you to control this behavior -- simply don't use this (e.g., by using a reference like instance in your example or via a closure).
Check out the context-setting functions Function.call and Function.apply to understand how a higher-order function like $.each sets the this context of a callback. Once you read those MDN pages, it might clear a few things up.
Here's a quick example:
Array.prototype.forEachWithContext(callback, this_in_callback) {
for(var i = 0; i < this.length; ++i) {
callback.call(this_in_callback, i, this[i]);
}
}
And to use it:
PageList.prototype.toHTML = function(){
//...
this.elements.forEachWithCallback(function(index, array_value){ ... }, this);
}
My example Array.forEachWithContext is similar to Array.forEach. However, it takes a callback and a second argument that is used as the value of this during the execution each of those callbacks.
Try wrapping your $.each function with a $.proxy like this...
$.each(this.elements, $.proxy(function(index, array_value){
console.log(this.url);
},this));
The $.proxy will ensure that this references your PageList...
I know that the problem was on the closure not taking this as the value of the instance, but as far as i know a reference to this inside a function that doesn't have an instance bound to it must refer to the window object, instead of undefined, at least that's the case on many of the browsers out there.
this is window. You're printing window.url, which is undefined. Try console.log(this), and it should yield window.
Given a function, like so:
var allev = new Array();
function events(index) {
allev[index] = $(this); // doesn't work
}
Is there any way to get this to work in the way above, or am I always restricted to:
var allev = new Array();
function events(index) {
// ...
}
function() { // out of scope, so make objects global
var count = allev.length;
var inst = events(count);
allev[count] = inst;
}
If there are any betters options than the two above, I'm open to those as well.
When you call functions (as opposed to methods) in JavaScript, this is the global object, or undefined in strict mode. So using this like you have above will never work.
You could do this:
function events(index) {
arr[index] = arguments.callee;
alert("Hi");
}
but that will break if you ever use strict mode (which is actually a really good idea).
You could also do this:
function events(index) {
allev[index] = this; // will work if you call with apply or call
}
If, and only if, you only ever call events like this:
events.call(events, 12);
call and apply both allow you to specify what this will be equal to inside of a function call. The difference is that call expects all arguments to be listed out, while apply expects all arguments to be passed in an array. So the above would be equivalent to
events.apply(events, [12]);
DEMO
I decided to look a little more into the jQuery API and I found $.proxy. It does exactly what I want, without having a method pass itself to make this work properly.
allev[index] = $.proxy(function() { return this; }, this);
I have an object and it has another inner object. How can I call the parent object from the inner object?
var test = {
init: function () {
var instance = this;
},
call: function() {
this.stop(); // works
},
stop: function() {
this.parseText(); // works
},
parseText: {
load: function ()
{
this.call(); //*** dont work
instance.call(); // work, but what if i have this instance (same name) on another object, would'nt this conflict it?
}
}
};
I'm using an instance, which works fine, but what if I or someone wrote an instance (same name) var in another object, wouldn't it will conflict and overwrite this instance?
Eric's answer gives you a reasonable example of how to do what you want to do, but doesn't really go into why.
In JavaScript, this is set entirely by how a function is called (for now; see below the fold for details), not where the function is defined as it is in some other languages that have the same keyword (Java, C++, C#, ...).
You, the coder, determine what this will be each time you call a function. There are two main ways: By calling the function via an object property (in the same expression), or explicitly using the function's built-in call and apply functions.
Via an object property
Using an object property:
obj.foo(); // or
obj["foo"](); // both work
That does two very distinct things, but which collaborate to set the this value: First, the function reference is found by looking up the foo property of the object obj. Then, the function is called. Because you called it as part of the same overall expression retrieving the property value, the JavaScript engine will set this to obj within the call.
So in your example, test.parseText.load(), within the load call this will be parseText, not test, because that's the object on which load was looked up.
Note that setting-this-via-property-lookup only works when they're done at the same time. This does not work:
var f = obj.foo;
f(); // `this` will not be `obj` within the call
That doesn't work because they weren't done at the same time. The property lookup and function call were separated.
Using call or apply
The second way of setting this is more explicit: All functions have the call and apply properties, which are themselves function references that call the function using information you supply. In both cases, the first argument is the object to use as this during the call. So if we wanted to fix the example above that didn't work, we could do this:
var f = obj.foo;
f.call(obj); // `this` will be `obj` within the call
f.apply(obj); // same
The only difference between call and apply is how you supply function arguments. With call, you supply them as further discrete arguments to the function; with apply, you pass in an array of arguments.
So these all do the same thing:
// 1 - Directly via property
obj.foo("a", "b", "c");
// 2 - Using `call`
f = obj.foo;
f.call(obj, "a", "b", "c");
// 3 - Using `apply`
f = obj.foo;
f.apply(obj, ["a", "b", "c"]); // Note the `[ ... ]`, one array with three elements
You can see how call and apply could work with your existing structure:
test.parseText.load.call(test.parseText);
That calls test.parseText.load, making this = test.parseText within the call.
What Eric did in his answer was to use a closure to make it simpler for you to call parseText with the this value you expect.
Further reading (disclosure: from my blog):
Mythical methods
You must remember this
Closures are not complicated
Up top I said:
In JavaScript, this is set entirely by how a function is called
(for now...
The reason I said "for now" is that in ES6, JavaScript is getting "arrow functions" and unlike other functions, the value of this within an arrow function is set by where they're created, not how they're called: They get this from the context where you create them.
Suppose you were writing code in an object method and wanted to use another method of the object to, I don't know, output information from an array (yes, this is contrived). In ES5, you'd probably do this:
this.output("Entries:");
theArray.forEach(function(entry, index) {
this.output(index + ": " + entry);
}, this);
// ^------- tells `forEach` what to use as `this` during the callback
If you left off the argument, you'd have a bug:
this.output("Entries:");
theArray.forEach(function(entry, index) {
this.output(index + ": " + entry); // <== Bug, `this` is either
// `undefined` (strict) or
// the global object (loose)
});
But since arrow functions inherit this from where they're created rather than getting it based on how they're called, the arrow function version of that doesn't need the second argument:
this.output("Entries:");
theArray.forEach((entry, index) => {
this.output(index + ": " + entry);
});
If all you're worried about is test changing, do it like this:
var test = (function() {
var object = {}
object.call = function() {
this.stop(); // works
};
object.stop = function() {
this.parseText(); // apparently works, even though parseText is not a function
};
object.parseText = {
load: function() {
object.call(); // works
}
};
return object;
})();
If you don't know the name of test, you can use a self-invoking anonymous function to create a wrapper, and refer to the object as shown below.
Note that test is not a reference to a function, but to the return value of the anonymous function. Because the object name (obj) is wrapped inside a function, it cannot be read or modified from outside
The solution below is neat, does not pollute the scope of test, and works like a charm. As mentioned earlier, test refers to the same object as obj. It's however not possible to manipulate variable obj, from outside, so that the code inside the function breaks.
var test = (function(){ //Self-executing function
var obj = {
call: function() {
this.stop(); // works
},
stop: function() {
this.parseText(); // works
},
parseText: {
load: function ()
{
obj.call(); // obj refers to the main object
}
}
};
return obj; //Return the object, which is assigned to `test`.
})(); //Invoke function
Update
It's not possible to reliably refer to self, this, or any reference to the object inside an object, without wrapping it.
Your current solution does not work, see comments in the code below:
var obj = {
init: function(){
var instance = this; //`instance` is declared using `var` inside a function
}, // This variable cannot read from "the outside"
parseText: {
load: function(){
instance.call(); //Does NOT work! instance is not defined
}
}
}
"call" is actually a built-in function on the function object that can be used to call the function specifying what to use for this. How does your code work? It doesn't seem like it should since parseText isn't a function...
Maybe try this:
parseText: function() {
var load = function ()
{
this.call(); //*** should work
};
load.call(this);
}
When I pass 'this' to an anonymous function like so:
MyClass.prototype.trigger = function(){
window.setTimeout(function(){this.onTimeout();},1000);
}
I get a "this.onTimeout is not a function"-error. I guess that 'this' is no longer available at the time the anonymous function is executing? So I've been doing this:
MyClass.prototype.trigger = function(){
var me = this
window.setTimeout(function(){me.onTimeout();},1000);
}
Is this really how you're supposed to do things? It kinda works, but it feels weird.
Then we have this example:
$(function(){
function MyClass(){
this.queue = new Array();
}
MyClass.prototype.gotAnswer = function(count){
$('body').append("count:"+count+"<br/>");
}
MyClass.prototype.loadAll = function(){
var count = 0;
var item;
while(item = this.queue.pop()){
count++;
var me = this;
$.getJSON("answer.html",{},function(data){me.gotAnswer(count);});
}
}
var o = new MyClass();
o.queue.push(1);
o.queue.push(2);
o.loadAll();
});
This outputs:
2
2
Shouldn't it output:
1
2
instead? Then I discovered that putting the $.getJSON-statement in another function makes it all work:
MyClass.prototype.loadAll = function(){
var count = 0;
var item;
while(item = this.queue.pop()){
count++;
this.newRequest(count);
}
}
MyClass.prototype.newRequest = function(count){
var me = this;
$.getJSON("answer.html",null,function(data){ me.gotAnswer(count); });
}
This outputs:
1
2
(Or the other way around.) What's happening here? What is the right way to pass variables to an anonnymous function?
Sorry for the confusing and lengthy post.
What you are experiencing is the correct behavior - it's not a good behavior, but it's part of the language. The value of "this" is reset inside every function definition. There are four ways to call a function that have different ways of setting "this".
The regular function invocation myFunc(param1, param2); This way of calling a function will always reset "this" to the global object. That's what's happening in your case.
Calling it as a method myObj.myFunc(param1, param2); This unsurprisingly sets "this" to whatever object the method is being called on. Here, "this" == "myObj".
Apply method invocation myFunc.apply(myObj, [param1, param2]) This is an interesting one - here "this" is set to the object you pass as the first parameter to the apply method - it's like calling a method on an object that does not have that method (be careful that the function is written to be called this way). All functions by default have the apply method.
As a constructor (with "new") myNewObj = new MyConstructor(param1, param2); When you call a function this way, "this" is initialized to a new object that inherits methods and properties from your function's prototype property. In this case, the new object would inherit from MyConstructor.prototype. In addition, if you don't return a value explicitly, "this" will be returned.
The solution you used is the recommended solution - assign the outside value of "this" to another variable that will still be visible inside your function. The only thing I would change is to call the variable "that" as Török Gábor says - that's sort of the de-facto standard and might make your code easier to read for other programmers.
You are confused about the closures.
For the first problem, yes, you are right, that is the way it can be done. The only difference that there is a convention to name the variable that that holds this.
MyClass.prototype.trigger = function(){
var that = this;
window.setTimeout(function(){that.onTimeout();},1000);
}
There is already a nice thread about this on StackOverflow. Check answers for question
How does a javascript closure work?.
Your second problem is an exact duplicate of Javascript closure inside loops - simple practical example.
you will have the same problem if inside your new method: newRequest you have to use a "for" or a "while" statement.
Another solution could be creating a closure:
like that:
$.getJSON("answer.html",{},(function(me){return function(data){me.gotAnswer(count);}})(this));