JavaScript and jQuery, "this" inside an anonymos function - javascript

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.

Related

Putting function in Array messes its parent

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

"this" reference inside of object function

I have the following JavaScript code:
function PatternField(id, name, pattern) {
...
this.check = function () {
var field = this.elem.val();
...
};
this.elem.keyup(this.check);
this.elem.change(this.check);
}
When the execution comes to check function var field = this.elem.val(); it turns out that this points to elem rather than actual object.
How can I access real this from inside this object function?
this.check = function() {
var field = this.elem.val();
...
}.bind(this);
The important part being bind(this) which controls the scope of the function once it is invoked/called (note that the function is not invoked immediately when using bind, you are manipulating the definition, if you will...); in this case, retaining the scope of PatternField. Check the docs regarding bind at MDN.
In other words (in regards to some comment that magically deleted itself):
It makes sure that the scope of this.check (when called) will be whatever is passed to the first parameter of bind, overriding whatever might naturally occur. If the you want this to reference PatternField within the this.check method, the bind method of Function will enable this capability.
Like #zamnuts answered, you can use the ES5 bind method.
But if you want to do it the old way, i.e., supporting old browsers without a polyfill, you can use:
var that = this;
this.check = function () {
var field = that.elem.val();
...
};

Javascript OOP - accessing the inherited property or function from a closure within a subclass

I am using the javascript inheritance helper provided here: http://ejohn.org/blog/simple-javascript-inheritance/
I have the following code, and I have problem accessing the inherited property or function from a closure within a subclass as illustrated below. I am new to OOP javascript code and I appreciate your advice. I suppose within the closure, the context changes to JQuery (this variable) hence the problem. I appreciate your comments.
Thanks,
-A
PS - Using JQuery 1.5
var Users = Class.extend({
init: function(names){this.names = names;}
});
var HomeUsers = Users.extend({
work:function(){
// alert(this.names.length); // PRINTS A
// var names = this.names; // If I make a local alias it works
$.map([1,2,3],function(){
var newName = this.names.length; //error this.names is not defined.
alert(newName);
});
}
});
var users = new HomeUsers(["A"]);
users.work();
this in the inner function
function(){
var newName = this.names.length;
alert(newName);
}
is not the same as this in the outer function.
work: function(){
$.map([1,2,3],function(){
var newName = this.names.length;
alert(newName);
});
}
There are many ways that people work around this:
Store a reference to outer function's this as another variable
work: function(){
var that = this;
$.map([1,2,3],function(){
var newName = that.names.length;
alert(newName);
});
}
As you see, that is being used instead of this.
Use jQuery's $.proxy
work: function(){
$.map([1,2,3],$.proxy(function(){
var newName = this.names.length;
alert(newName);
}, this));
}
What $.proxy does is it creates another function that calls the function you passed in (in this case, the inner function), but explicitly set the context of the function (this) to the second arguments.
Use Function.prototype.bind
work: function(){
$.map([1,2,3],function(){
var newName = this.names.length;
alert(newName);
}.bind(this));
}
It works just like jQuery's $.proxy, but in this one, you call the bind method of the function.
It isn't supported on all browsers, but there is a JavaScript implementation of Function.prototype.bind on MDC. You can use it.
this in a confusing keyword, and if you want to learn more about this, then look at this.
The this variable is not like the others. Normal variables are always available to their child scopes, but this is, by default, set to the global window object when a function is called:
alert(this.names); # Works; this == yourObject
$.map([1,2,3],function(){
alert(this.names); # Doesn't work; this == window.
});
The documentation for jQuery.map() confirms this:
this will be the global window object.
Thus, the solution is to set another variable to this so it will be available in the child scope:
var that = this;
$.map([1,2,3],function(){
alert(that.names);
});
I recommend viewing Douglas Crockford's presentation Function the Ultimate, in which he shows all sorts of crazy things you can do with functions and objects in JavaScript.

function used from within javascript dojo closure using "this"-notation is undefined

I have a Problem accessing an Object function using "this".
In case of the below example (which is simplified, because I cannot supply actual code due to various reasons) the function call this._getEntry() is "undefined" when calling
createList().
I would hope for some opinions on wether this is due to a misunderstanding of
javascript closures or rather a syntax error.
In the latter case I will have to find the error in the actual code myself.
If it is a misunderstanding of javascript or dojo concepts I would really appreciate
some help on how to correctly scope and access the below mentioned function (_getEntry()).
var OBJECT = {
_getEntry : function(entry){
var li = document.createElement('LI');
li.appendChild(document.createTextNode(entry));
return li;
},
createList : function(entryArray){
var list = document.createElement('UL');
dojo.forEach(entryArray,function(entry){
list.appendChild(this._getEntry(entry));
});
dojo.body().appendChild(list);
}
};
OBJECT.createList(["entry1","entry2"]);
thanks!
Firstly, I think your pasted code is missing ); to complete the forEach.
Secondly, forEach takes an optional third parameter which determines the context in which the passed function runs. If not given, it defaults to the global scope, so yes this is your problem. Assuming this already refers to what you need it to immediately outside the forEach, you should be able to just pass this in as the third argument to forEach and it should work, e.g.:
dojo.forEach(entryArray, function(entry){
list.appendChild(this._getEntry(entry));
}, this);
For more information: http://dojotoolkit.org/api/dojo/forEach - which is based on the API in JS 1.6 https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/forEach
This is a common problem and you are right, its all about scoping and closure. What is happening here is once you get into the forEach the context changes to the global context (window). Handily, Dojo provides a way to set this context:
createList : function(entryArray){
var list = document.createElement('UL');
dojo.forEach(entryArray,function(entry){
list.appendChild(this._getEntry(entry));
}, this);
dojo.body().appendChild(list);
}
An alternate approach is to use closure to get at this
createList : function(entryArray){
var list = document.createElement('UL');
var _this = this; // closure allowing the forEach callback access to this
// some popular variable names used for this include:
// $this, _this, me and that
dojo.forEach(entryArray,function(entry){
list.appendChild(_this._getEntry(entry));
});
dojo.body().appendChild(list);
}

How is data passed to anonymous functions in JavaScript?

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

Categories