Coffescript closure _this reference lost within nested loops - javascript

Here is sth interesting on CoffeeScript I observed.
TLDR: {
We know that the fat arrow (=>) generates a closure saving the reference to this and every reference of # would be substituted with the original value of this. Thus the following coffeescript code:
=>
#sth
would produce the following:
(function(_this) {
return (function() {
return _this.sth;
});
})(this);
Notice the _this.sth.
}
But here is the corner case I found:
=>
for a in sth
for b in #sth
sth
Which evaluates to:
(function(_this) {
return (function() {
var a, b, i, len, results;
results = [];
for (i = 0, len = sth.length; i < len; i++) {
a = sth[i];
results.push((function() {
var j, len1, ref, results1;
ref = this.sth;
results1 = [];
for (j = 0, len1 = ref.length; j < len1; j++) {
b = ref[j];
results1.push(sth);
}
return results1;
}).call(_this));
}
return results;
});
})(this);
This is a bit long, but the problem is that the inner loop itterates through this.sth instead of _this.sth.
The exact lines of the inner loop are:
ref = this.sth;
results1 = [];
for (j = 0, len1 = ref.length; j < len1; j++) {
b = ref[j];
Is this the normal behaviour, or is it a bug?

Look at the inner loop more closely:
results.push((function() {
var j, len1, ref, results1;
ref = this.sth;
// Loop stuff goes here...
}).call(_this));
The inner loop is wrapped in a function (as part of the loop comprehension code) which is evaluated using Function.prototype.call:
The call() method calls a function with a given this value and arguments provided individually.
call is called with _this (the stashed/bound # from the =>) so this inside that function is actually _this and all is well.
If you suppress the comprehension code by explicitly returning nothing:
=>
for a in sth
for b in #sth
sth
return
then you'll see the ref = _this.sth that you were originally expecting:
(function(_this) {
return (function() {
var a, b, i, j, len, len1, ref;
for (i = 0, len = sth.length; i < len; i++) {
a = sth[i];
ref = _this.sth; # <---------------------------
for (j = 0, len1 = ref.length; j < len1; j++) {
b = ref[j];
sth;
}
}
});
})(this);

Related

Returning an array of functions: creating closures

Given this function call:
var funcs = obj.getClosures([2, 4, 6, 8], function(x) {
return x*x;
});
I have the following function:
getClosures : function(arr, fn) {
var funcs = [];
var array = arr;
var i = 0;
var l = array.length;
(function(i, array) {
for (; i < l; i++) {
funcs[i] = function(i, array) {
return fn(array[i]);
};
}
}(i, array));
return funcs;
},
I'd like to be able to loop through the returned array and get the square root values of each item in the array exactly like this:
for (var i = 0; i < arr.length; i++) {
funcs[i]();
}
results each time through loop : 4, 16, 36, 64
Shouldn't my funcs array have a function reference in each index that can be readily invoked with the relevant argument values? Where did I go wrong?
There multiple "issues":
The IIFE (function(i, array) { ... }(i, array)); has no benefit at all here. If you remove it the code will have the exact same behavior. If you want to capture the current value of i and array, you would have to move it inside the for loop.
Your function definition is incorrect.
funcs[i] = function(i, array) {
return fn(array[i]);
};
Inside the function, array[i] will refer to the arguments you pass to the function. If you don't pass any, they will be undefined and the code will throw an error. That is, with that definition, you would have to execute the functions like so:
for (var i = 0; i < arr.length; i++) {
funcs[i](i, array);
}
which kind of defeats the purpose of generating the functions in the first place.
If you want to create a closure which has access to i and array of the scope where the function was defined in, don't define parameters with the same name.
Possible solution:
for (var i = 0, l = array.length; i < l; i++) {
(function(i) {
funcs[i] = function() {
return fn(array[i]);
};
}(i));
}
Or simpler, if your code runs in environments which support .map:
getClosures: function(arr, fn) {
return arr.map(function(v) {
return function() {
fn(v);
};
});
},
Related questions:
JavaScript closure inside loops – simple practical example
How do JavaScript closures work?
Read about Function.prototype.bind:
var obj = {
getClosures : function(arr, fn) {
var funcs = [];
var array = arr;
var i = 0;
var l = array.length;
(function(i, array) {
for (; i < l; i++) {
funcs[i] = function(i, array) {
return fn(array[i]);
}.bind(this,i,array);
}
}(i, array));
return funcs;
}
}
var funcs = obj.getClosures([2, 4, 6, 8], function(x) {
return x*x;
});
for (var i = 0; i < funcs.length; i++) {
console.log(funcs[i]());
}
Outputs:
4
16
36
64
It works since javascript 1.8.5 (firefox 4). I have no idea for other browsers but there is implementation for older versions (should work on older browsers as well)

Closures in Javascript: assigning local variable doesn't work

Just when I thought I understood closures...
The following code snippet:
function f() {
var a = [];
var i;
for (i = 0; i < 3; i++) {
a[i] = function () {
var x = i;
return x;
}
}
return a;
}
var a = f();
console.log(a[0]());
console.log(a[1]());
console.log(a[2]());
prints out 3, 3, 3. I don't understand why. I'm copying the value of 'i' to the local variable x, so there should be three x's: x0=0, x1=1. x2=2. How are all of them reading the final value of i?
Your problem is caused by each a[i] being, in fact, a closure. They all share the same i, which is evaluated when each a[i] is called, not when the loop executes. You need to create each closure with a separate context. For instance:
function f() {
var a = [];
var i;
for (i = 0; i < 3; i++) {
a[i] = makeClosure(i);
}
return a;
}
function makeClosure(i) {
return function () {
var x = i;
return x;
}
}
Even though the value of i changes in your for loop, it's still the same i variable. You need to shadow i in that scope and effectively pass it by value:
for (var i = 0; i < 3; i++) {
(function(x) {
a[x] = function() {
return x;
}
})(i);
}

Is it possible to define and initialize multidimensional arrays in JavaScript in one line of code?

Sorry if this is too basic, but I am struggling at defining 4-dimensional array (of size 6x6x6x6) in JavaScript and initializing it to all 1's. What's the easiest way to do this?
Thanks!
You can use the literal syntax, but it would be very big and cumbersome. You may want to try something like this:
var x = [1, 1, 1, 1, 1, 1];
for (var i = 1; i < 4; i++) {
x = [x, x, x, x, x, x];
}
I found a slightly simpler solution:
var x = 1;
for (var i = 0; i < 4; i++) {
x = [x, x, x, x, x, x];
}
Seems like there should be easier way, but this will do it.
var array = [];
for(var i=0; i<6; i++) {
for(var j=0; j<6; j++) {
for(var k=0; k<6; k++) {
for(var l=0; l<6; l++) {
array[i][j][k][l]=1;
}
}
}
}
Edit
To generate an n-dimensional AxBxCxDx... array (untested):
Array.prototype.fill = function(elem, n) {
for(var i=0; i<n; i++, this.push(elem));
}
function generateArray() {
var dimensions = Array.prototype.slice.call(arguments);
var x = 1;
for (var i = dimensions.length-1; i >= 0; i--) {
x = [].fill(x, dimensions[i]);
}
return x;
}
to generate a 2x3x4x5 matrix:
generateArray(2,3,4,5);
I implemented ddlshack's generalized method, but ran into an issue due to the fact that arrays are "pass by reference" in JavaScript. This resulted in each dimension of the array holding multiple references to the same array rather than copies of it. To correct the issue, I implemented the solution as follows (the only other difference being that I used a second function rather than modify Array's prototype).
var fillArray = function(val, dim) {
var a = [];
for (var i = 0; i < dim; i++) {
if (Object.prototype.toString.call(val) === "[object Array]") {
val = val.slice(0);
}
a.push(val);
}
return a;
};
var generateArray = function() {
var dimensions = Array.prototype.slice.call(arguments),
val = 0;
for (var i = (dimensions.length - 1); i >= 0; i--) {
val = fillArray(val, dimensions[i]);
}
return val;
};

Array.Splice prototype

I have a addAfter and addBefore function that adds a new element to an array. This array is my storage that other functions use. Basically I am storing low level objects that define table cells. After the element is added to the array I then have to insert the value of the html property of the element the table row.
Is there a way to prototype my array to handle both operations rather than me having to double up on the work load every time I addAfter or addBefore, with-out messing up the prototype of the native array?
var bays=[];
addAfter: function (b, n) {
for (var i = 0, ii, len = bays.length; i < len; i++) {
ii = i + 1; if (ii == n) {
bays.splice(ii, 0, b);
var newCell = canvsTrBay.insertCell(ii);
newCell.outerHTML = b._html;
};
};
this.build();
}
Is it possible to do something like:
bays.prototype.add=function(b,n,isAfter){
for (var i = 0, ii, len = bays.length; i < len; i++) {
ii =(isAfter? (i + 1):(n>0?i-1:0);
if (ii == n) {
bays.splice(ii, 0, b);
var newCell = canvsTrBay.insertCell(ii);
newCell.outerHTML = b._html;
};
};
this.build();
}
You can add it directly to the object itself:
bays.add = ...;

Javascript slice.call(arguments) and recursion

I have a simple recursive javascript function that can be called with additional arguments:
AllDataRows(grid.Rows, process);
AllDataRows(grid.Rows, process, storeIDs);
The problem is that if the function has to call itself then any additional arguments are lost. I tried using Array.prototype.slice.call(arguments, 2) to pass the arguments along, but they end up as one element arrays. The cb function then fails because it isn't expecting an array (it would be a hidden textbox).
How can I resolve this?
Thanks
function AllDataRows(rowList, cb) {
if (rowList.getRow(0).GroupByRow) {
for (var i = 0; i < rowList.length; i++)
AllDataRows(rowList.getRow(i).Rows, cb);
} else {
var args = Array.prototype.slice.call(arguments, 2);
for (var j = 0; j < rowList.length; j++)
cb.apply(rowList.getRow(j), args);
}
}
function AllDataRows(rowList, cb) {
if (rowList.getRow(0).GroupByRow) {
for (var i = 0; i < rowList.length; i++) {
var aa = Array.prototype.slice.call(arguments, 0);
aa[0] = rowList.getRow(1).Rows;
AllDataRows.apply(this, aa);
}
} else {
var args = Array.prototype.slice.call(arguments, 2);
for (var j = 0; j < rowList.length; j++)
cb.apply(rowList.getRow(j), args);
}
}
Just use apply when you make the recursive call, fixing up the argument array to account for the sub-group you're opening up.

Categories

Resources