I have a model js file which looks like
goog.provide('model.ErrorLogger');
/**
* #constructor
*/
model.ErrorLogger = function() {
window.onerror = goog.bind(this.errorHandler, this);
this.doInitialSend();
};
goog.addSingletonGetter(model.ErrorLogger);
model.ErrorLogger.prototype.ws_ErrLgr_config = true;
model.ErrorLogger.prototype.doInitialSend = function(){
if (this.ws_ErrLgr_config){
window.setInterval(this.sendReport, this.ws_ErrLgr_config);
}
};
model.ErrorLogger.prototype.sendReport = function(){
// the value of 'this' needs to be of the ErrorLogger model and not windows
if (!this.ws_ErrLgr_config || this.stopped) {
//some more code here
}
}
In the constructor I call the doInitialSend function which set the window.setInterval. Now in the sendReport function the the value of 'this' is not correct. How to correctly pass 'this' to get the correct value instead of getting window's this.
I tried storing the the value of this in a reference but that didn't work either. For example
var that = this;
window.setInterval(that.sendReport, that.ws_ErrLgr_config);
The idiomatic way to do this in Google Closure is using goog.bind, with the advantage that it's guaranteed it's always going to work. Plus it will use Function.prototype.bind() under the hood when that is available.
In that case the solution will be:
myIntervalInMilliseconds = 1000; // One second.
window.setInterval(goog.bind(this.sendReport, this), myIntervalInMilliseconds);
Using that = this works, but requires you to explicitly wrap your function within another one to capture that as this within the desired function.
It's much better using Function.prototype.bind() for that, as pointed out in the other answers. However this won't work if you care to support older browsers (IE < 9).
PS: Another issue in your code is that it is using this.ws_ErrLgr_config as the interval, which is set to true in the prototype. This is incorrect, you should pick a number to represent your interval.
Or this:
window.setInterval((function() {
this.sendReport();
}).bind(this), this.ws_ErrLgr_config.ReportInterval);
You can do this:
var that = this;
window.setInterval(function() {
that.sendReport()
}, this.ws_ErrLgr_config.ReportInterval);
That way you can call sendReport in the correct context, also this works:
window.setInterval(this.sendReport.bind(this), this.ws_ErrLgr_config.ReportInterval);
The reason why window.setInterval(that.sendReport, this.ws_ErrLgr_config.ReportInterval) doesn't work is because Javascript is pass-by-value. The above statement is the equivalent to:
window.setInterval(function(){
// the value of 'this' needs to be of the ErrorLogger model and not windows
if (!this.ws_ErrLgr_config || this.stopped) {
//some more code here
}
}, this.ws_ErrLgr_config.ReportInterval);
By using the .bind() keyword or wrapping it in another function and then refering to a thatfrom the outerscope, you can call the function in the desired scope.
Related
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();
...
};
I am a relatively experienced c# (and before that c++ Win32) developer, I am new to javascript and have a question regarding the this pointer.
I am using knockout.js, and one function called subscribe accepts a this variable, that will be set inside the callback function.
From my way of thinking from the Win32 days and C#, on any callback function i want a scope object which contains my state.
In this case I have use the this javascript thing to set my callback scope.
My questions are:
Now everything works (full fiddle here if you are
interested), but have I done something terrible?
Is there any reason this is used instead of passing in an explicit
scope variable as a parameter (that would make things easier to understand as
for me, this makes the workings kind of hidden).
What is the intended use for this?
From http://knockoutjs.com/documentation/observables.html it says:
The subscribe function accepts three parameters: callback is the function that is called whenever the notification happens, target (optional) defines the value of this in the callback function, and event (optional; default is "change") is the name of the event to receive notification for. Example below
myViewModel.personName.subscribe(function(oldValue) {
alert("The person's previous name is " + oldValue);
}, null, "beforeChange");
My code snippet below:
var computedOptions = createComputedDepdency(viewModel[option.requires.target],option.data);
viewModel[option.optionsName] = computedOptions;
console.log("making callback scope object for: " + option.optionsName );
var callbackScope = {
callbackName: option.optionsName,
options: computedOptions,
selectedValue: viewModel[option.selectedName]
};
// when the list of available options changes, set the selected property to the first option
computedOptions.subscribe(function () {
var scope = this;
console.log("my object: %o", scope);
scope.selectedValue(scope.options()[0].sku);
console.log("in subscribe function for..." + scope.callbackName);
},callbackScope);
First a semantic note:
The scope of a function is not related to this word. The context is related to this. The scope is related to the accessibility of variables and functions inside another function.
When you try to read a variable outside the function where it's declared, then you trying to access to a var outside its scope. So you cannot do it because the var is inside a scope not accessible from current position.
Now everything works (full fiddle here if you are interested), but have I done something terrible?
If it works, it's not so terrible :-)
Is there any reason this is used instead of passing in an explicit scope variable as a parameter (that would make things easier to understand as for me, this makes the workings kind of hidden).
a fast read: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this
In javascript the value of this is determined by how a function is called.
In one way this approach could save annoying passage of context as argument: in a well documented library, the this use is very intituitive.
In other cases, I agree when you change continually context in your app without a rigorous logic, it could be confused.
What is the intended use for this?
We should always remember how and when the javascript is born. It was born for browser in order to interact with the DOM.
For this purpose, the context has sense that change based of which element call the function.
For example:
var divs = document.getElementsByTagName('DIV');
for(var i = 0; i < divs.length; i++) {
divs[i].addEventListener('click',_clickHandler);
}
function _clickHandler() {
this.innerHTML = "clicked";
}
DEMO http://jsfiddle.net/AYBsL/1/
This is an example to how is useful the implicit change of context in javascript.
You could do this also for user-defined function: when you call a function you could change the context:
_clickHandler.call(divs[0]); // simulate click of first div
In javascript 'this' refers to the object that called your function. Only in a situation when you use 'new' keyword you can expect it to point to the current object (function).
var MyObject = function () {
this.hello = function () { console.log(this); }
}
var instance = new MyObject();
There is a way to make sure that this is always what you expect and that is creating a variable to store the correct reference for you and use that instead of this... in your example it would be similar to this...
computedOptions = function () {
var that = this;
}
computedOptions.subscribe(function () {
console.log("my object: %o", scope);
scope.selectedValue(that.options()[0].sku);
console.log("in subscribe function for..." + that.callbackName);
},callbackScope);
MDN JavaScript reference would inevitably explaing it more better then myself, have a look at it.
You shouldn't mix scope and this. this is supposed to mimic classical-oop languages like java or++, that is to keep the reference to an instance object. But it can be used just to execute arbitrary function on a given context using .apply() or .call.
What about scope, you don't have to do anything to pass the scope to a function, since the outer scope becomes automatically accessible inside function. You should read about closures - it's the best part of 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.
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);
Lately I've been trying to make an object in JavaScript with the following structure:
function colorDiv(div){
this.div=div;
this.div.bind("click",this.changeColor)
this.changeColor(){
this.div.css("background", "#FF0000");
}
}
The problem is that the changeColor method cannot be called from the jQuery environment because this must refer to the current colorDiv object, so the bind method cannot work as expected.
How can this be solved?
There are a couple ways. The simplest is as follows:
function ColorDiv(div) {
var that = this;
that.div = div;
that.div.bind("click", that.changeColor);
that.changeColor = function () {
that.div.css("background", "#FF0000");
};
}
var colorDiv = new ColorDiv($("#my-div"));
$("#something-else").click(colorDiv.changeColor);
You save a reference to this in the variable that, which is just the name commonly used in the JavaScript world for exactly this purpose. Then you refer to that instead of this inside your changeColor method. (Note that I used that everywhere, just for consistency, even though the only place it actually makes a difference is inside the changeColor method.)
Another is to use the Function#bind method. You can either use it every time you call changeColor, like so:
var colorDiv = new ColorDiv($("#my-div"));
$("#something-else").click(colorDiv.changeColor.bind(colorDiv));
or you can use it in the ColorDiv class to ensure that all methods are bound correctly whenever they are called:
this.changeColor = (function () {
this.div.css("background", "#FF0000");
}).bind(this);
As noted in the linked article, Function#bind is not supported in all browsers, so you'll need a shim like the one they give, or possibly a full-fledged ES5 shim.
Underscore.js has a bindAll function that could be useful here, too, especially with multiple methods:
_.bindAll(this);
Finally, it's worth noting you don't really need to do any of this in your particular example: just do
this.changeColor = function () {
div.css("background", "#FF0000");
};
instead of what you have, i.e. reference the div variable passed in, instead of the reference stored in this.div, since they are the same thing.
Try setting as the first line of the method:
var self = this;
and then using self as needed.
this.div.bind("click",self.changeColor)