JavaScript (node.js) variable not accessible on inner function call. now.js - javascript

I am using now.js and Mongoose in a node project and am having trouble accessing the this.now object inside of a mongoose function. E.g.
everyone.now.joinDoc = function (project_id){
this.now.talk(); //this will work
Project.findOne({'_id':project_id}, function(err, project){
if(project){
this.now.talk(); // this will not work "TypeError: Cannot call method 'bark' of undefined"
};
});
};

Change the code to this:
everyone.now.joinDoc = function (project_id){
this.now.talk(); // this will work
var that = this; // save 'this' to something else so it will be available when 'this' has been changed
Project.findOne({'_id':project_id}, function(err, project){
if(project){
that.now.talk(); // use local variable 'that' which hasn't been changed
};
});
};
Inside your inner function, the this is probably being set to something else. So, to preserve the value you want to access, you assign it to a different local variable that will be available in the inner function.

everyone.now.joinDoc = function (project_id){
this.now.talk(); // this will work
Project.findOne({'_id':project_id}, (function(tunnel, err, project){
if(project){
this.now.talk();
};
}).bind(this, "tunnel")); // overwrite `this` in callback to refer to correct `this`
};
Use Function.prototype.bind to set the value of this to the value you want

Related

Ajax-assigned variable inaccessible after assignment

So, I have a script that fetches data from a SQL database and I'm trying to build a JS wrapper for it. I'm using the following functions to call that script and use information from the DB as soon as it's ready.
var User = function() {
this.email = null;
//Async call to get user values
this.current(function(response) {
this.email = response.email;
//The value is assigned/usable at this point
});
};
User.prototype.current = function(callback) {
$.post("php/db_functions.php", {object: "CurrentUser"}).done(function(result) {
callback(JSON.parse(result)[0]);
});
};.
Everything seems to work fine, but if I try to access the value from the object after I've created it, it returns undefined, like so:
var me = new User();
//And then, way after the async call and when me.email should be defined
me.email //Returns undefined
Why can I use it in the callback, but not afterwards?
In a function, the context variable this points to either the global window object or to undefined in the strict mode, unless specified otherwise by the caller. Therefore, you need to either capture the value of this in a local variable:
//Async call to get user values
var that = this;
this.current(function(response) {
that.email = response.email;
});
or call the function in the desired context using either the call or the apply method:
User.prototype.current = function(callback) {
var that = this;
$.post("php/db_functions.php", {object: "CurrentUser"}).done(function(result) {
callback.call(that, JSON.parse(result)[0]);
});
};.
Also, as others have mentioned, there is no guarantee the AJAX request will have finished by the time the User contructor returns.
This is a timing bug since the variable is not assigned until the async call returns. You can't access email right away.

Why in Javascript the this context changes based on way of calling?

While this issue occurred to me specifically with KnockoutJS, my question is more like a general javascript question.
It is good to understand however that ko.observable() and ko.observableArray() return a method so when assigning a value to them, you need to call the target as method instead of simply assigning a value to them. The code that I'm working with should also support plain objects and arrays, which I why I need to resolve to a method to call to assign a value to the target.
Think of these 2 examples:
Non-working one (this context changed in called method):
// Assigning value to the target object
var target;
// target can be of any of thr following types
target = ko.observableArray(); // knockout observable array (function)
// target = ko.observable(); // knockout observable (function)
// target = {}; // generic object
// target = []; // generic array
//#region resolve method to call
var method;
if (isObservable(target)) {
// if it is a knockout observable array, we need to call the target's push method
// if it is a konckout observable, we need to call the target as a method
method = target.push || target;
} else {
// if target is a generic array, we need to use the array's push prototype
// if target is a generic object, we need to wrap a function to assign the value
method = target.push || function(item){ target = item; };
}
//#endregion
// call resolved method
method(entity);
Working one (this context is fine):
if (isObservable(target)) {
if (target.push) {
target.push(entity);
} else {
target(entity);
};
} else {
if (target.push) {
target.push(entity);
} else {
target = entity;
};
}
Now, to the actual question:
In the first approach, later in the execution chain when using a knockout observable knockout refers to this context within itself, trying to access the observable itself (namely this.t() in case someone is wondering). In this particular case due to the way of callin, this has changed to window object instead of pointing to the original observable.
In the latter case, knockout's this context is just normal.
Can any of you javascript gurus tell me how on earth my way of calling can change the 'this' context of the function being called?
Ok, I know someone wants a fiddle so here goes :)
Method 1 (Uncaught TypeError: Object [object global] has no method 'peek')
Method 2 (Works fine)
P.S. I'm not trying to fix the code, I'm trying to understand why my code changes the this context.
UPDATE:
Thanks for the quick answers! I must say I hate it when I don't know why (and especially how) something is happening. From your answers I fiddled up this quick fiddle to repro the situation and I think I got it now :)
// So having an object like Foo
function Foo() {
this.dirThis = function () {
console.dir(this);
};
};
// Instantiating a new Foo
var foo = new Foo();
// Foo.dirThis() has it's original context
foo.dirThis(); // First log in console (Foo)
// The calling code is in Window context
console.dir(this); // Second log in console (Window)
// Passing a reference to the target function from another context
// changes the target function's context
var anotherFoo = foo.dirThis;
// So, when being called through anotherFoo,
// Window object gets logged
// instead of Foo's original context
anotherFoo(); // 3rd log
// So, to elaborate, if I create a type AnotherFoo
function AnotherFoo(dirThis){
this.dirThis = dirThis;
}
// And and instantiate it
var newFoo = new AnotherFoo(foo.dirThis);
newFoo.dirThis(); // Should dir AnotherFoo (4th in log)
If you're after a way to choose the 'this' that will get used at the time of call,
you should use bind, that's exactly done for that.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
So if SomeObject has a push method, then storing it like this won't work :
var thePushMethod = someObject.push;
since you loose the context of the function when writing this.
Now if you do :
var thePushMethod = someObject.push.bind(someObject);
the context is now stored inside thePushMethod, that you just call with
thePushMethod();
Notice that you can bind also the arguments, so for instance you might write :
var pushOneLater = someObject.push.bind(someObject, 1 );
// then, later :
pushOneLater(); // will push one into someObject
Consider this example,
function Person () {
this.fname = "Welcome";
this.myFunc = function() {
return this.fname;
}
};
var a = new Person();
console.log(a.myFunc());
var b = a.myFunc;
console.log(b());
Output
Welcome
undefined
When you make a call to a.myFunc(), the current object (this) is set as a. So, the first example works fine.
But in the second case, var b = a.myFunc; you are getting only the reference to the function and when you are calling it, you are not invoking on any specific object, so the window object is assigned. Thats why it prints undefined.
To fix this problem, you can explicitly pass the this argument with call function, like this
console.log(b.call(a));
So, for your case, you might have to do this
method.call(target, entity);
Check the fixed fiddle

trying to pass `this` through a function

I have a function localised to the main function and i want to use this to call it but it doesn't seem to work.
My code has:
function option(room,slot){
var div_id = document.getElementById(room);
var opacity = window.getComputedStyle(div_id).opacity
transition_opacity(div_id,opacity,0,function(){this.load});
function load(){
console.log('test'); //does not happen
}
}
Have i misunderstood the use of this or is the scope lost when i use function(){} to call load?
From your code it is not obvious, what object this could refer to. It depends on how option is called. However, if you define the load function inside of the option function anyway, it is best to just reference it directly. You will have to move the declaration of test above the transition_opacity call though:
function option(room,slot){
var div_id = document.getElementById(room);
var opacity = window.getComputedStyle(div_id).opacity;
function load() {
console.log('test');
}
transition_opacity(div_id,opacity,0,load);
}
As you can see, I just reference load directly. You could make another function which calls the load function inside (i.e. function() { load(); } – note the parentheses which calls the function) but that would give you no benefit but would just add another unneeded function to the stack. So just refer to the actual function itself.
For more information on the this keyword, check out this question. Spoiler: It’s more complicated than you would expect.
The scope of this is lost in this instance, probably pointing to the document. You can capture this to a variable in the outer scope to make this work as intended.
var context = this;
transition_opacity(div_id,opacity,0,function(){context.load();})
The above will not work however. This is because load does not exist on the context of this. You would need to define the load function as such:
context.load = function(){
console.log('test');
}
Both.
First, your load function is not a member/property of any this, the way you have it coded. Your load function is simply a nested function that exists within your option function, as has been sort of implicitly noted in other responses.
In your option function, if you want 'load' to become a member of 'this', you'd need to say so, like this:
function option(){
this.load = function(){}; // now load is actually a property of whatever this is
}
Second, you and the other poster are correct that 'this' is no longer the same 'this' by the time your anonymous function is called.
Whenever you call a function, a brand new 'this' is created and exists within the scope of that function. If you just call a function like this:
transition_opacity(args);
.. then within transition_opacity, 'this' just refers to the window object, or maybe window.document. For 'this' to refer to anything other than window or window.document, you need to (in effect) do one of the following:
myObject.transition_opacity(args);
transition_opacity.call(myObject, arg1, arg2, ..);
transition_opacity.apply(myObject, argArray);
or
var myObject = new transition_opacity(args);
In each of those cases, within transition_opacity, 'this' refers to myObject (or, well, in the last case, it refers to a new object that is being created and assigned to myObject).
Here is a way to do what it looks like you're trying to do:
var MyNamespace = {
option: function(room,slot){
var div_id = document.getElementById(room);
var opacity = window.getComputedStyle(div_id).opacity;
var _this = this;
transition_opacity(div_id,opacity,0,function(){
// Careful! Inside here, 'this' is just window or window.document,
// unless transition_opacity sets it to something using call or apply,
// in which case that 'this' is probably not the 'this' you want.
// So carefully refer to the saved instance of 'this':
_this.load();
});
},
load: function(){
console.log('test'); // now it should happen
}
}
.
.
MyNamespace.option(room, slot); // inside option, 'this' is MyNamespace.
Here's another way to do it:
function MyClass(){};
MyClass.prototype = {
// all the same stuff that is in MyNamespace above..
};
.
.
var myObject = new MyClass();
myObject.option(room, slot);
Clear as mud?
Just use
transition_opacity(div_id,opacity,0,load);
You have defined a 'load' within another function as an 'Function Declaration', so now it is only accessible within 'option' function and in other functions defined in this one by name 'load'. You can't access it by using 'this.load' no matter what 'this' is. If you want to access 'load' function as 'this.load' you can try this example to understand how 'this' keywoard works
// Function Declaration
function f1(callback){
callback();
};
// Function Declaration
function f2(){
// Function Expression
this.load = function(){
console.log("test");
};
f1(this.load);
};
var obj = new f2(); // test, this == obj, so obj.load() now exists
obj.load(); //test, this == obj
f2(); //test, this == window, so window.load() now exists
load(); //test, window is the global scope

call a js function based on the argument passed

I'm trying to call a js function within another one, but use the argument to specify the function. ie depending on the argument passed, it will call a different function
function toggle(n){
if (sessionStorage['toggle'+n]== 0){
check+n();
}
else
}
So, for example, if the argument 'Balloons' was passed as n, then it will call the function checkBalloons(). "check+n();" is not currently working here. Sorry for my lack of simple js syntax!
If the function is defined in the global scope (browser) you can do:
window["check"+n]();
or some tenants like Node.js you would access it from global object.
global["check"+n]();
if it is a part of some other object then you would do the same.
obj["check"+n]();
Functions and properties defined on an object can be accessed using [] convention as well. i.e obj["propFuncName"] will give you reference to it, so in case of methods you add () to invoke it.
If the function is global, you would do this:
window["check" + n]();
or, you could put your function in an object like so:
myNamespace = {};
myNamespace.checkSomething = function(){ /* ... */ }
// call it like this:
myNamespace["check" + n]();
The answers thus far are correct, but lack explanation.
In JavaScript, you cannot call a function by name when that name is a string. What you can do is retrieve a value from an object by name, and if that value happens to be a function, you can then call it. For example:
var myObject = {};
myObject.myFunction = function() { alert('test!'); };
// Get the property on `myObject` called `myFunction`
var theFunctionLookup = myObject['myFunction'];
// Since that property was a function, you can call it!
theFunctionLookup();
In the browser, functions that are defined in the global scope are attached to the window object. For example, this works:
function myFunction() { alert('test'); }
var theFunctionLookup = window['myFunction'];
theFunctionLookup();
You can shorten the last two lines into one:
function myFunction() { alert('test'); }
// Look up and call the function in one line.
window['myFunction']();
For the same reasons, you can use a dynamically-calculated string to look up functions.
function checkBalloon() {
alert('checking balloon');
}
function toggle(n){
if (sessionStorage['toggle'+n]== 0){
window['check' + n]();
check+n();
}
}
toggle('Balloon');
if you do this way:
if (sessionStorage['toggle'+n]== 0){
window["check" + n]();
}
will work

How do undefined or remove a javascript function?

I defined a global Javascript function:
function resizeDashBoardGridTable(gridID){
var table = document.getElementById('treegrid_'+gridID);
.....
}
After this function was used a few times, I want to remove(or undefined) this function because the Procedure code should be called again. if somebody try to call this method we need do nothing.
I don't way change this function right now.
so re-defined this function may be one way:
function resizeDashBoardGridTable(gridID){
empty,do nothing
}
Thanks. any better way?
Because you're declaring it globally, it's attached to the window object, so you just need to redefine the window function with that name.
window.resizeDashBoardGridTable = function() {
return false;
}
Alternately you could redefine it to any other value or even to null if you wanted, but at least by keeping it a function, it can still be "called" with no detriment.
Here's a live example of redefining the function. (thanks TJ)
An additional reason for pointing out that I'm redefining it on the window object is, for instance, if you have another object that has that function as one if its members, you could define it on the member in the same way:
var myObject = {};
myObject.myFunction = function(passed){ doSomething(passed); }
///
/// many lines of code later after using myObject.myFunction(values)
///
/// or defined in some other function _on_ myObject
///
myObject.myFunction = function(passed){}
It works the same either way, whether it's on the window object or some other object.
how about using a var?
// define it
var myFunction = function(a,b,c){
console.log('Version one: ' + [a,b,c].join(','));
}
myFunction('foo','bar','foobar'); // output: Version one: foo,bar,foobar
// remove it
myFunction = null;
try { myFunction(); console.log('myFunction exists'); }
catch (e) { console.log('myFunction does not exist'); }
// re-define it
myFunction = function(d,e,f){
console.log('Version two: ' + [d,e,f].join(','));
}
myFunction('foo','bar','foobar'); // output: Version two: foo,bar,foobar
OUTPUT:
[10:43:24.437] Version one: foo,bar,foobar
[10:43:24.439] myFunction does not exist
[10:43:24.440] Version two: foo,bar,foobar
The simplest approach is to set the function (treat it as a variable) to null. This works even if you don't declare it as a var. Verified this on IE.
resizeDashBoardGridTable = null
If the functions needs to be called 1 time you use an anonymous self invoking function like this:
(function test(){
console.log('yay i'm anonymous');
})();
If you have to call the function multiple times you store it into a var and set it to null when you're done.
Note: You don't have to name an anonymous function like I named it test. You can also use it like this:
(function(){
console.log('test');
})();
The reason I do name my anonymous functions is for extra readability.

Categories

Resources