I have a question to a specific behavior of javascript:
I have an object which I want to fill with generated functions. Each function contains a variable which is changed during the loop of function generation.
My problem is that the variable does not get replaced when assigning the function to the object. Instead the reference to the variable stays in the function and when executing the function only the last value of the variable is remembered.
Here is a minimal example (also on jsfiddle: http://jsfiddle.net/2FN6K/):
var obj = {};
for (var i = 0; i < 10; i++){
var nr = i;
obj[i] = function(){
console.log("result: " + nr);
}
}
for (var x = 0; x < 10; x++){
obj[x]();
}
The second loop executes all generated functions and all print a 9 as result. But i want that they print the value which the variable had at the time of generation (0, 1, 2, ...).
Is there a way to do this? Thanks in advance.
One approach is to call a function that returns a function:
function makeFunc(i) {
return function() {
console.log("result: " + i);
}
}
for (...) {
obj[i] = makeFunc(i);
}
Another approach is the immediately invoked function expression:
for (i = 0; ...; ...) {
(function(i) {
obj[i] = function() {
console.log("result: " + i);
}
})(i);
}
where in the latter case the (function(i) ... )(i) results in a permanent binding of i passed as a parameter to the outer function within the scope of the inner function
The problem is that all the functions you create are sharing a reference to the same nr variable. When you call them they fetch the value using that reference and therefore all of them get the same result.
Solve it like this:
for (var i = 0; i < 10; i++){
(function(nr) {
obj[nr] = function(){
console.log("result: " + nr);
}
})(i);
}
Your surmise is correct, and yes, there's a solution:
obj[i] = function( nr_copy ) {
return function() {
console.log("result: " + nr_copy);
};
}( nr );
JavaScript variables are scoped at the function level, unlike some other block-structured languages. That is, the fact that you declare "nr" inside the for loop doesn't make it "local" to that block โ the effect is precisely the same as if you'd declared it at the top of the function.
By introducing another function scope with that anonymous function, you make a new copy of the value of "nr", which is then privately accessible to the actual function that's returned. Each of those functions will have it's own copy of the value of "nr" as it stood when that slot of the "obj" array was initialized.
what you want is to create a closure for every function you create.
Yet, the var(s) have not a block scope, so your code is the same as :
var obj = {};
var i;
var nr;
for (i = 0; i < 10; i++){
nr = i;
obj[i] = function(){
console.log("result: " + nr);
}
}
which hopefully makes it more obvious all functions will refer to the very same 'nr' var.
What you want to do implies creating a new scope each time, which might be done using bind, but let's stick to your original intent and build a new closure each time with a lambda :
var obj = {};
for (var i = 0; i < 10; i++) {
obj[i] = (function(){
var this_nr = i;
return function(){
console.log("result: " + this_nr);
}
}() );
}
Related
I'm currently learning javascript and I would appreciate some help.
I've been playing around trying to create a program that would multiply two numbers without using the * operator. I already found an easier way to do it than my approach but I'd like to know why the code I wrote doesn't work:
function addToItself(a) {
a = a + a;
return a;
}
function times(b) {
for (count = 0; count < b; count++) {
addToItself(a)
}
return a;
}
function multiply (x, y) {
a = x;
times(y);
}
let output = multiply(5, 2);
alert(output);
Is it not working because the binding "a" in the addToItself function has a local scope and the multiply function can't read it or is it something else?
Thanks a lot in advance!
The issue is with the scope of each variable. In JavaScript, a variable declated within a function is scoped to that function. This means that a variable declared within a function can only be accessed within the function. Scopes are nested, so a variable declared globally is accessible inside a function too, though that's often discouraged.
Additionally, function arguments (such as a in addToItself and b in times) are treated like variables scoped to the function.
I would advise looking at the MDN docs for "scope" and familiarizing yourself with how variables are scoped in JavaScript.
I have included a fixed version of your code is below, for reference:
function addToItself(a) {
// I used a new variable here since reassigning to an argument is discouraged
const twoA = a + a;
return twoA;
}
console.log('5 + 5 = ' + addToItself(5));
function times(value, times) {
let temp = 0;
for (let count = 0; count < times; count++) {
temp += value;
}
return temp;
};
console.log('5 * 5 = ' + times(5, 5));
No you can't read variable inside another function, there are easier way, like
function multiply(x, y) {
var result = 0;
for (var count = 0; count < y; count++) {
result += x
}
return result;
}
console.log("5 * 2 = " + multiply(5, 2));
I'm in Chapter 3 - Functions of Eloquent Javascript.
Everything about the following code makes sense to me -- except one thing.
Why don't the sub-functions (for lack of a better term) have return statements?
var landscape = function() {
var result = "";
var flat = function(size) {
for (var count = 0; count < size; count++)
result += "_";
};
var mountain = function(size) {
result += "/";
for (var count = 0; count < size; count++)
result += "'";
result += "\\";
};
flat(3);
mountain(4);
flat(6);
mountain(1);
flat(1);
return result;
};
console.log(landscape());
// โ ___/''''\______/'\_
Maybe I'm missing something fundamental about the purpose of the return statement, even after reading different definitions from different sources.
I've tried adding return statements to the sub-functions. I've found that it'll either terminate the sub-function pre-maturely, or produce the same result as if it was never there.
Thanks for reading.
They are accessing the outer scoped variable result and change something there. So they change the actual result variable's value and doesn't need to return anything. If it reaches to the end of the function, function ends. You can do the same by creating local function scoped variables and make strings from them, return them and concatenate in the main landscape function.
See the alternative of your code with the return statements
var landscape = function() {
var result = "";
var flat = function(size) {
var localFlat = ''; // Function scoped variable
for (var count = 0; count < size; count++) {
localFlat += "_";
}
return localFlat; // Return function scoped variable
};
var mountain = function(size) {
var localMountain = "/"; // Function scoped variable
for (var count = 0; count < size; count++) {
localMountain += "'";
}
localMountain += "\\";
return localMountain; // Return function scoped variable
};
result = flat(3) + mountain(4) + flat(6) + mountain(1) + flat(1); // Concatenate the results of each function
return result;
};
console.log(landscape());
Because your sub-functions affect your global landscape variable - result and in the end, function return result variable..This example shows you that you can change global variables into sub-functions..
A little while ago, I asked a question using this sample JS code...
for (var myindex = 0; myindex < mylist.length; myindex += 1) {
var copyindex = myindex;
MyAsync(mylist[myindex], function () { alert(copyindex); });
}
(The answer was essentially to call a named function, which keeps each variable in the loop separate.)
My question for today is, what's actually going on in this sample code above? Each alert call is being passed the same copyindex variable, even though a new one is being declared each time the loop goes around.
Is this behavior required by the JS language? (That by declaring a variable with block scope, I'm really declaring it at the top of the function.)
Or, is this perhaps an implementation detail of the few browsers I tested with, and future JS platforms are under no obligation to link the many instances of copyindex together?
Each alert call is being passed the same copyindex variable, even though a new one is being declared each time...
That's a common misunderstanding. var does not have block scope, so your code really is this:
var copyindex;
for (var myindex = 0; myindex < mylist.length; myindex += 1) {
copyindex = myindex;
MyAsync(mylist[myindex], function () { alert(copyindex); });
}
More (on my blog): Poor, misunderstood var and, of course, in the specification โ it's covered in ยง10.5 - Declaration Binding Instantiation.
ES6 will introduce block-scoped variables via the let keyword. Where you use the let keyword matters quite a lot.
Let's take a simpler example, starting with var:
for (var x = 0; x < 3; ++x) {
setTimeout(function() { console.log("x = " + x); }, 0);
}
console.log("typeof x = " + typeof x);
We know that will give us
number
3
3
3
...because var is hoisted to the top of the scope, and so the same x is used by all three functions we create, and x exists outside the loop. (We see the typeof result first, of course, because the others happen after the minimal timeout.)
If we use let instead, in that same place:
for (let x = 0; x < 3; ++x) {
setTimeout(function() { console.log("x = " + x); }, 0);
}
console.log("typeof x = " + typeof x);
We get
undefined
3
3
3
...because x is scoped to the loop, not loop iterations. All three functions still use the same x, the only difference is that x doesn't exist outside the loop.
But if we use let within the body:
for (let n = 0; n < 3; ++n) {
let x = n;
setTimeout(function() { console.log("x = " + x); }, 0);
}
console.log("typeof x = " + typeof x);
We get
undefined
0
1
2
...because now x is scoped to the body of each iteration, not the loop as a whole.
You can try these yourself using NodeJS, just be sure to give it the --harmony flag to enable ES6 features.
window.TicTacToet.compMove = function (row, col) {
var player = window.TicTacToet.PlayerTurn;
var board = window.TicTacToet.Board;
for (i = 0; i < window.TicTacToet.Board.length; i++) {
for (j = 0; j < window.TicTacToet.Board[i].length; j++) {
if (window.TicTacToet.Board[i][j] == null) {
getWin(row, col, player, board);
} else {
console.log("position occupied");
}
}
}
}
function getWin($r, $c, $player, $board) {
checkTop($r, $c, $player, $board);
}
function checkTop($x, $y, $player, b) {
console.log("ENTER");
var success = false;
for (i = 0; i < 3; i++) {
$x--;
if ($x < 0) {
return success;
}
if (b[$y][$x] != $player) {
return success;
}
}
success = true;
return success;
}
The function check-Top is executing infinite times. The parameters row and col are co-ordinates of the table. Player will return true or false and the board is an array having 9 elements. Both window.TicTacToet.Board.length and window.TicTacToet.Board[i].length have value 9 , i.e 9 x 9 .The console.log("ENTER") should execute 91 times but it is executing infinite times.what may be the reason for this. Because of this all other functions are not working properly, game itself not playing.Please Help out. This is for 9 x 9 clickable board drawing game.
I guess you might want to use var keyword for variable i,
because you are using same variable name i at two for loops. So, at the second for loop, you are unintentionally overwriting i of the first for loop. To avoid this, you can declare variables with var keyword, which defines variable scope.
Change
for(i=0;i<window.TicTacToet.Board.length;i++)
To
for(var i=0;i<window.TicTacToet.Board.length;i++)
And Change
for (i=0;i<3;i++)
To
for (var i=0;i<3;i++)
JavaScript has function-level scope. When you declare variables within a function, they are only accessible within that function. The code below explains how variable scope works in JavaScript:
Without var keyword.
i = 100;
function foo(){
i = 0; // overwriting i to 0
}
foo();
alert(i); // shows 0
With var keyword.
var i = 100;
function foo(){
var i = 0; // This defines a new variable 'i' in the scope of function foo(){}.
}
foo();
alert(i); // shows 100
With var keyword ( in nested functions )
var i = 100;
function foo(){
var i = 200; // A new variable 'i' in the scope of function foo(){}.
function bar(){
var i = 0;// A new variable 'i' in the scope of function bar(){}.
}
bar();
alert(i); // shows 200
}
foo();
alert(i); //shows 100
In most languages which have block-level variable scope, variable are accessible whithin their block surrounded by curly brackets ({and}). But JavaSciprt doesn't terminate scopes at the end of blocks, but terminate them at the end of functions.
I'm sure there are many articles and documents about it. I googled it and found an intresting introductory article.
http://javascriptissexy.com/javascript-variable-scope-and-hoisting-explained/
Hope this helps.
Let's say that I want to get a list of all the variables in the window that are user-defined. In other words, they're not properties or objects that the browser has created or defined in ECMAScript.
For example, let's say there's this script on a page:
<script>
window.__$DEBUG = true;
var Analytics = function() {};
</script>
I would like to be able to loop through window and get a list containing __$DEBUG and its value, and Analytics and its value:
var nonNatives = (function nonNative(scope) {
var result = {};
for (var child in scope) {
if (!isNative(child)) {
result[child] = scope[child];
}
}
return result;
})(window);
Can this be done?
I've previously done this by creating a single function (loaded before any other JS) which remembers the current keys of window (i.e. the built-in properties) and which when called again displays the differences.
If the purpose is just to detect accidentally global variables, the aforementioned function can be an anonymous IIFE (such that it doesn't itself pollute the global scope) which contains the current list in scope, which then periodically calls another enclosed function (with setTimeout) to compare the list, and update it for next time, e.g:
(function(scope) {
var keys = Object.keys(scope);
var map = {};
for (var i = 0, n = keys.length; i < n; ++i) {
map[keys[i]] = 1;
}
(function update() {
var current = Object.keys(scope);
// compare lists and print the differences
for (i = 0, n = current.length; i < n; ++i) {
var name = current[i];
if (!(name in map)) {
console.log(name + ' = ' + scope[name]);
map[name] = 1;
}
}
// loop
setTimeout(update, 1000);
})();
})(window);