Very confused about JavaScript scopes - javascript

Below is a greatly simplified snippet of code derived from a SlickGrid sample:
model.js:
(function($) {
function RemoteModel() {
var someArr=[0];
var someVar=0;
var onDataLoadedSuccess = new Slick.Event();
function ensureData(from, to) {
h_request = setTimeout(function() {
req = $.ajax({
url: url,
context: this,
success: function(resp) {
onSuccess();
}
});
}, 3000);
}
function onSuccess() {
someVar=someVar+100;
someArr[0]=someArr[0]+100;
console.log(someVar + " " + someArr[0]); // yes values are changing properly for both !
onDataLoadedSuccess.notify({ from: from, to: to });
}
return {
"someArr": someArr,
"someVar": someVar,
"onDataLoadedSuccess": onDataLoadedSuccess,
};
}
})(jQuery);
main.js:
var loader=new RemoteModel();
var grid=new Slick.Grid("#wlGrid", loader.data, columns, options);
grid.onViewportChanged.subscribe(function(e, args) {
var vp = grid.getViewport();
loader.ensureData(vp.top, vp.bottom);
});
loader.onDataLoadedSuccess.subscribe(function(e, args) {
// someVar is always 0 :( ! someArr[0] is changing fine .. why ???
console.log(loader.someVar + " " + loader.someArr[0]);
});
So an event on the grid (onViewportChanged) is calling ensureData on the model, which
increments both someVar and someArr[0], both of which are exposed by the return value
of RemoteModel.
After incrementing the event, onDataLoadedSuccess is triggered by the model, however
within that event only the value of loader.someArr[0] has changed.
why is the value of someVar in onDataLoadedSuccess always zero? Is that a scope issue?
And why is the value of someArr[0] changing fine?
How can I properly read the value of someVar in onDataLoadedSuccess?

The RemoteModel function has local variables for someVar and someArr. These are also returned as the object properties when loader is created. The difference is that the value of the number for someVar is effectively copied into loader when it is created.
The array is copied into someArr as well, but the copy is by reference. Thus you have two things pointing to the same array. When the event executes, it updates the local variable and the local array. loader points to the same array, so you see someArr[0] change, but it does not "point to" the number, so when the local someArr is changed the loader value is not.
Edit to address the comment
One option is to define your returned object first and then have your private functions reference it. This could be limited to numbers or apply to all public members; I would do the latter for clarity. With this approach, only the class needs to change, not the code calling it.
function RemoteModel() {
var ret = {
"someArr": [0],
"someVar": 0,
"onDataLoadedSuccess": new Slick.Event()
};
function onSuccess() {
ret.someVar=ret.someVar+100;
// ...
}
return ret;
// ...
A second option is to create a getSomeVar method that is in your returned object so that outside the class you do loader.getSomeVar() instead of just loader.someVar. (And you could create a setSomeVar(newValue) if it needs to be externally settable.
function RemoteModel() {
var someVar=0;
// ...
function getSomeVar() {
return someVar;
}
return {
"someArr": someArr,
"getSomeVar": getSomeVar,
"onDataLoadedSuccess": onDataLoadedSuccess,
};
}
and then:
loader.onDataLoadedSuccess.subscribe(function(e, args) {
console.log(loader.getSomeVar() + " " + loader.someArr[0]);
});

Numbers and other non-object primitives get returned by value, not by reference:
return {
"someArr": someArr, // <-- object
"someVar": someVar, // <-- number
"onDataLoadedSuccess": onDataLoadedSuccess,
};
someArr is an object (array), which will be returned by reference. However someVar is just a simple number. Thereby you're returning a value.
Instead of returning an object in your RemoteModel you should use RemoteModel as a proper constructor:
this.someArr=[0];
this.someVar=0;
this.onDataLoadedSuccess = new Slick.Event();
/* move other functions into the prototype, get rid of the return */
If you're not familiar with using a function as a constructor see Working with Objects on MDN.

It is because how javascript is handling the values.
(function($) {
function RemoteModel() {
this.someArr=[0];
this.someVar=0;
this.onDataLoadedSuccess = new Slick.Event();
var _this = this;
function ensureData(from, to) {
h_request = setTimeout(function() {
req = $.ajax({
url: url,
context: this,
success: function(resp) {
onSuccess();
}
});
}, 3000);
}
function onSuccess() {
_this.someVar=_this.someVar+100;
_this.someArr[0]=_this.someArr[0]+100;
console.log(_this.someVar + " " + _this.someArr[0]); // yes values are changing properly for both !
_this.onDataLoadedSuccess.notify({ from: from, to: to });
}
}
})(jQuery);

When you created a model, you returned this to loader:
return {
"someArr": someArr,
"someVar": someVar,
"onDataLoadedSuccess": onDataLoadedSuccess,
}
loader.someArr is pointing to the same array pointed by someArr in the closure. someVar, however, is a property with a value of 0, copied from someVar in the closure.
When assigning a variable or object property with an object (including arrays), you are referencing to that object. Thus, loader.someArr is pointing to the same array pointed by someArr in the closure. Changes can be done via both someArr and loader.someArr since they point to the same array.
But in the case of someVar, you assigned loader.someVar from someVar. Since 0 is a primitive value, the value is copied from someVar to loader.someVar. Thus, someVar and loader.someVar don't point to the same thing. Modifying someVar does not modify loader.someVar or vice versa.

Thats because at the Moment you return
{
"someArr": someArr,
"someVar": someVar,
"onDataLoadedSuccess": onDataLoadedSuccess,
};
someVar is 0.
someVar holds a String, a primitive type.
someArr references an Array a non Primitive type.
Strings (and other primitive types)
are passed by value, Arrays (and other non-primitive types) by reference. More specific: the variables reference to the Object gets passed by value (when passed the new var holds a copy of the reference)
So what you return is a "copy" of your String

Related

Javascript closures - lifetime of variables

Being fairly new to Javascript and from a c# background I have been stumbling along adequately. I knew that soon enough I would need to get my head round the fact that functions are objects in their own right and that JS closures are often the cause of confusion.
I am trying to understand this little snippet of code
// Function which returns object with function properties
function myFunc() {
value = 42;
var result = {
value: value,
getValue: getValue,
incrementValue: incrementValue,
setValue: setValue,
};
return result;
function setValue(y) {
value = y;
};
function getValue() {
return value;
};
function incrementValue() {
value++;
};
};
// Helper function to print out results
function printResults(m,x){
$('#output').append(m + ': ' + x).append('<br/>');
};
var myObject = myFunc(); // returns the object
printResults('Inital call to getValue',myObject.getValue());
myObject.setValue(59);
printResults('Called changeValue',myObject.getValue());
printResults('Value property of object',myObject.value);
printResults('Called getValue again',myObject.getValue());
myObject.incrementValue();
printResults('Call increment value',myObject.getValue());
printResults('Value property of object',myObject.value);
I get the following results when run in jsFiddle
Inital call to getValue: 42
Called changeValue: 59
Value property of object: 42
Called getValue again: 59
Call increment value: 60
Value property of object: 42
These show that the functions are using the variable value within their closure and this persists between invocation of the inner functions. BUT, the value of value does not change in the returned object.
I think I get the basic point that functions are executed using the scope chain that was in effect when they were defined.
Questions
Can I make the value property of the returned object operate in the same way - or is the only way to return it via a function, since the latter retains the variable in its closure?
And, just for confirmation, for every invocation of myFunc(), I assume I will get an object whose function properties will have their own scope chain and therefore independent of each invocation.
First of all, do not forget the var keyword when declaring variables. When you declare value = 42 inside myFunc, you are actually creating a variable in the global namespace instead of the function scope. It should start like this:
function myFunc() {
var value = 42;
Now, myObject.result is returning 42 because myFunc returns your result object which contains a copy of the value variable declared inside the function.
Your functions setValue, getValue and incrementValue are changing the value of value, not result.value. When you call myObject.value, you are getting the value from the returned object, not the inner variable of your function.
You could get it to work using something like this:
function myFunc() {
var value = 42;
var result = {
value: value,
getValue: getValue,
incrementValue: incrementValue,
setValue: setValue
};
return result;
function setValue(y) {
result.value = y;
}
function getValue() {
return result.value;
}
function incrementValue() {
result.value++;
}
}
However, there are better design patterns than this. You could use the new keyword and prototype to define the methods available for the objects returned from your function. Take this example:
function myFunc() {
this.value = 42;
}
myFunc.prototype.setValue = function(y) {
this.value = y;
}
myFunc.prototype.getValue = function(y) {
return this.value;
}
myFunc.prototype.incrementValue = function(y) {
this.value++;
}
var myObject = new myFunc();
console.log(myObject.getValue()); // 42
myObject.setValue(30);
myObject.incrementValue();
console.log(myObject.getValue()); // 31
Yes, you can:
var result = {
get value() {
return value;
},
getValue: getValue,
incrementValue: incrementValue,
setValue: setValue,
};
Hooray for ECMAScript 5. Of course, this won’t work on IE < 8.
<aside>value = 42; should be var value = 42;.</aside>
This doesn’t have a lot to do with the lifetime of variables, by the way – it’s just how assignment works. There are references in JavaScript, but no “reference variables” or “reference properties”. The object contains a copy of whatever value was at the time; creating a getter like this is just like creating a function that’s called implicitly.
Can I make the value property of the returned object operate in the same way
If you mean that it shows the updated value, yes, you can do that. You just have to change the code to update the value property as well:
function myFunc() {
var value = 42; // don't forget var!
var result = {
value: value,
getValue: getValue,
incrementValue: incrementValue,
setValue: setValue,
};
return result;
function setValue(y) {
result.value = value = y;
}
function getValue() {
return value;
}
function incrementValue() {
value++;
result.value = value;
}
}
The reason why I choose to use both value and result.value is to prevent the modification of the value through result.value. If you notice, I don't internally read from result.value, I only write to it. That means that assignments to result.value from external code doesn't have an effect. This conforms to how your existing code works.
And, just for confirmation, for every invocation of myFunc(), I assume I will get an object whose function properties will have their own scope chain and therefore independent of each invocation.
Yes, every invocation of myFunc creates a new object and new functions and they are completely independent from objects/functions created by previous invocations.

return a value from object in js

Assume I have a simple object in js with one private variable:
function test(){
var value=true;
}
and now I want to create one instance:
var r=new test() //I want to get r === true
How can I return a value from it?
If I write:
function test(){
var value=true;
return value;
}
I have a test {} in result.
If I write:
function test(){
var value=true;
return function(){ return value; }
}
then I can get the value, but I must add additional parentheses:
var r=new test()() //r === true
I don't want the parentheses, so I tried to change the code to:
function test(){
var value=true;
return (function(){ return value; } )();
}
But in response, again I get test {}
How to write the return statement in this situation?
I believe you need to do something like:
function test(){
this.value = true;
}
and then
var r=new test();
if (r.value == true) {
//Do something
}
First I feel obliged to clarify a possible misunderstanding:
function test(){
var value=true;
}
is not an object with a private variable. It is a function with a local variable. When you call the function with new, it creates an object inheriting from the functions's prototype with no properties. If you call the function normally, it simply executes the function body and returns undefined (since you are not returning anything).
Solutions:
Do you actually need a constructor function? I'm asking because your example is very simple. Obviously you cannot have the function return two values, true and the object.
So, you could just call the function without new:
function test() {
var value = true;
return value;
}
var r = test();
If you really want r to be true then I see no reason to call the function as a constructor function.
The reason why you got test {} as result was because you called the function with new. If you do that, the function will always return an object and if you don't do so explicitly (value is a boolean, not an object), it implicitly returns this (which is an object).
So again, if you really want r to be equal to value from inside the function, then simply don't call the function with new.
If you need an object though, there are a couple of ways:
You can assign the value to a property and access it instead, like PokeHerOne showed in his answer or add a function which returns that value, as papaiatis demonstrates. The advantage is that the value is accessed explicitly and other people looking at your code understand what's going on.
Additionally, depending on what you want to do with that value / object, you can implement the valueOf methods, which gets called by various operators.
For example:
function Test(){
var value = true;
this.valueOf = function() {
return value;
}
}
var t = new Test();
console.log(t); // logs the Test instance
console.log(t == true); // logs `true`
I.e. t is an object but behaves like the value true (value) in various operations. This is powerful but can also be quite confusing, since the type conversion is somewhat implicit and it's not something that is used in JavaScript very often.
Used methods defined internally:
function TestClass(){
var value = true;
this.getValue = function(){
return value;
};
}
var t = new TestClass();
alert(t.getValue()); // true
Since value is defined as private it is not accessible from outside:
alert(t.value) // undefined

Javascript classes and variable references

I'm trying to solve this puzzle minded Javascript OOP problem.
So I have the following class :
var ClassA = function() {
this.initialize();
}
ClassA.prototype = {
methods : ['alpha','beta','gama'],
initialize : function() {
for ( var i in this.methods ) {
this[this.methods[i]] = function() {
console.log(this.methods[i]);
}
}
}
}
var a = new ClassA();
When I call every method I expect to print the name of it, right? But here is what i get :
a.alpha(); // returns gama ?!?
a.beta(); // returns gama ?!?
a.gama(); // returns gama
But when my class looks like this :
var ClassB = function() {
this.initialize();
}
ClassB.prototype = {
methods : ['alpha', 'beta', 'gama'],
initialize: function() {
for ( var i in this.methods ) {
this.addMethod(this.methods[i]);
}
},
addMethod: function(method) {
this[method] = function() {
console.log(method);
}
}
}
var b = new ClassB();
b.alpha(); // returns alpha
b.beta(); // returns beta
b.gama(); // returns gama
Why is this happening ?
for ( var i in this.methods ) {
this[this.methods[i]] = function() {
console.log(this.methods[i]);
}
}
Your problem lies here. When this loop ends, i is the last element. Each function uses the same i, so they are all the last element.
When you use addMethod you are making a closure to "capture" the correct value.
EDIT: When you call addMethod you are "copying" the value, instead of using the i value, which changes with each loop iteration.
In your first version:
initialize : function() {
for ( var i in this.methods ) {
this[this.methods[i]] = function() {
console.log(this.methods[i]);
}
}
}
The methods that you create within initialize all refer to the same i variable from initialize - and after initialize runs i has the value "gama", so regardless of which of the methods you call that's the value of i that they'll log to the console. JS doesn't store the current value of i at the time the method is created.
JS creates a "closure" for each function - variables declared in your initialize function (i.e., i) continue to be in scope for the nested function(s) even after initialize has finished.
The second version calls addMethod to add each method:
addMethod: function(method) {
this[method] = function() {
console.log(method);
}
}
...and so when they run they'll refer to their own "copy" of the method parameter because then there is a separate closure for each of the methods.
Edit: See also this question: How do JavaScript closures work? (several answers there explain this more clearly than I did).
You can fix your first example by adding an anonymous closure:
initialize : function() {
for ( var i in this.methods ) {
(function (i) { // anonymous closure
this[this.methods[i]] = function() {
console.log(this.methods[i]);
}
}).call(this, i); // use .call() if you need "this" inside
}
}
Now it will work the same way as your second example. "Anonymous" means that the closure is made by function which doesn't have a name and is called instantly as it is "created".
Note sideways: use .call(this, ...) to preserve this inside the called function, or you can do var that = this, use that instead of this and call the function normally:
for ( var i in this.methods ) {
var that = this;
(function (i) { // anonymous closure
that[that.methods[i]] = function() {
console.log(that.methods[i]);
}
})(i); // Called normally so use "that" instead of "this"!
}
Well, first of all stop using for (property in object) loops on Arrays. It's all fun and games until somebody prototypes to the Array object which is both a perfectly reasonable and very useful/popular thing to do. This will result in custom methods getting added to your for x in array loops.
As for the problem, it's doing exactly what you told it to do in version 1. The problem is that by the time you get around to firing it, i is the last thing i was, 'gamma'. When you pass a reference into a function as an argument, the function holds on to the value's state as it was passed.

How to procedurally set Javascript getter/setters? (or actually closure scopes)

I'm trying to procedurally add getters/setters to objects in Javascript and although I think the code below should simply work, it doesn't act as I expected.
here is my code:
var data = {a:1, b:2, c:3};
function Abc(data) {
var data = data || {};
for ( var key in data ) {
console.log(key, data[key]);
this.__defineGetter__(key, function() {
console.log('using getter');
return data[key];
})
}
return this;
}
abc = Abc(data);
console.log('this should be 1', abc.a);
console.log('this should be 2', abc.b);
console.log('this should be 3', abc.c);
and this is my unexpected output
a 1
b 2
c 3
using getter
this should be 1 3
using getter
this should be 2 3
using getter
this should be 3 3
the output makes absolutely no sense to me, but I get the same output on Chrome and Webkit, so I'm guessing I'm just stupid and this is not a bug of the Javascript engines :)
as the comments mention my triple use of "data" isn't really good!
By the time the closure that was passed to __defineGetter__ is executed, the loop has finished and key remains at the last value. Try this:
function Abc(data) {
if(!(this instanceof Abc)) {
return new Abc(data);
}
data = data || {};
for(var key in data) {
(function(key) {
console.log(key, data[key]);
this.__defineGetter__(key, function() {
console.log('using getter');
return data[key];
});
}).call(this, key);
}
return this;
}
Some other things:
You weren't using var on key, so key was global. That wasn't causing your issue, but it's a good idea to add it anyway.
You were declaring a new variable named data when there was already a data in the scope of the function. It's not needed to use var there since there's already a data in the function; I removed it.
You weren't using new, which was also causing odd behavior; the new three lines at the top of the function cause it to act as if it was called with new.
#Robert Gould. main problem in your code sample is not in "defining getters and setters", but in "understanding closures".
See more about closures in MDN
Additionaly your code is invalid on using this keyword, because your Abc() function this object points to global window object. You must use new Abc() to properly use this keyword or must create new empty object inside Abc() and return it.
1)
function Abc(data) {
data=data||{};
for(key in data) {
console.log(key, data[key]);
(function(data,key) { // defining closure for our getter function
this.__defineGetter__(key, function() {
console.log('using getter');
return data[key];
});
}).call(this,data,key);
}
// dont need to return: when used *new* operator *this* is returned by default
}
var abc = new Abc(data);
or
2)
function Abc(data) {
data=data||{};
var obj={};
for(key in data) {
console.log(key, data[key]);
(function(data,key) { // defining closure for our getter function
obj.__defineGetter__(key, function() {
console.log('using getter');
return data[key];
});
})(data,key);
}
return obj; // return object
}
var abc = Abc(data);
If you realy need to known about "defining getters|setterns" read about:
Object.defineProperty() MDN and get operator MDN
and for back compatibility __defineGetter__ MDN
you must also understand what not all top used browsers currently supports getters and setters for properties
key is a local variable in the scope of Abc, the anonymous function you wrote has no variable key, so it uses the one from the outer scope. That variable has changed to the last value in the loop by the time the anonymous function is used, so you see the last value. You can fix this with a closure:
this.__defineGetter__(key, (function(l_key){
return function() {
console.log('using getter');
return data[l_key];
}
})(key));
In that code l_key is a local copy of key to the anonymous function returned be another anonymous function.

closure not working

var Dog = function() {
var _instance = 'hello world';
return function() {
console.log(this._instance);
}
} (); //note that it is self invoking function
var l = new Dog(); //#> undefined
In the above case I was expecting an output of:
'hello world'
Why is this._instance not accessing the the variable which should be accessible by virtue of closure? I tested this in FF and am getting undefined.
You don't assign _instance to the object, it's just a closure variable, and should be accessed without using this:
var Dog = function() {
var _instance = 'hello world';
return function() {
console.log(_instance);
}
} (); //note that it is self invoking function
var l = new Dog();
I'd probably write it like so instead:
var Dog = (function() {
var defaults = {
name: 'Rags'
};
var Dog = function (options) {
// Take options as a constructor argument, this
// allows you to merge it with defaults to override
// options on specific instances
this.setOptions(options);
};
Dog.prototype = {
// Common methods for Dogs here
setOptions: function (options) {
// Declare all variables in the beginning of the method because
// JavaScript hoists variable declarations
var key = null;
// First assign all the defaults to this object
for ( key in defaults) {
this[key] = defaults[key];
}
// Now override with the values in options:
if (options && options.hasOwnProperty) {
for ( key in options ) {
this[key] = options[key];
}
}
}
};
return Dog; // Return the constructor method
} ()); // wrap the self-invoked function in paranthesis to visualize that
// it creates a closure
var buster = new Dog({name: 'Buster'}),
unnamed = new Dog();
alert(buster.name); // Alerts 'Buster'
alert(unnamed.name); // Alerts 'Rags'
Note that I have not tried to compile the above code, so it might contain a few mistakes. Nothing JsLint can't handle though!
You might want to consider adding filtering to the setOptions method so that it doesn't assign properties you don't want, or filter out methods etc declared in the options-parameter.
Additionally, if you use JQuery, or similar library, there are (often) utility functions for merging objects, making it trivial to write the setOptions-method:
function setOptions (options) {
// I assume JQuery here
// true as the first argument gives us a recursive merge
var mergedOptions = $.extend(true, defaults, options);
for (var key in mergedOptions ) {
if(this.checkAllowedProperty(key, typeof(mergedOptions[key])) {
this[key] = mergedOptions[key];
}
}
}
/**
* This method checks if propertyName is an allowed property on this object.
* If dataType is supplied it also checks if propertyName is allowed for
* dataType
* #return true if propertyName, with type dataType, is allowed on this object,
* else false
*/
function checkAllowedProperty (propertyName, dataType);
Your problem is this.
Change this._instance to _instance. You may also want to wrap your self-invoking function in parentheses like (function() { ... })(); for maximum browser compatibility.
As the others have said, you need to remove "this." from your function.
The reason for the problem is down to the binding of the "this" keyword in the two function contexts. Inside the closure, "this" refers to the function that is being returned, and not to the outer function. You could resolve this by doing the following:
var Dog = function() {
var _instance = 'hello world';
var that = this; //Assign "this" to "that"
return function() {
console.log(that._instance); //Use reference to "that"
}
} ();
var l = new Dog();
You could also probably do something closer with the function.apply() method, but I'll leave that to you.
I hope that helps.
Perhaps you are satisfied by removing "this.", but you may be interested to learn that "this" doesn't refer to what you wanted it to anyway. What it refers to really depends on how the function is called. It does not necessarily refer to an instance of an object constructed by the function you returned, or its container function, or to any other object. By default, if you merely call the function as a normal function, "this" will refer to the global window context.
What you must do to have "this" be bound to any specific object is to call the function as a method of that object, or of its prototype. e.g. foo.myMethod(). Another way is that you can use the apply or call method, passing in the object you want it to apply to. e.g. anyFunction.apply(foo).

Categories