I'm using RequireJs and for production releases combining/minify-ing and uglify-ing it using r.js, but have run into a bug related to how it handles one of my FOR loops.
I turned off all optimizations on the r.js build so now it is only combining the code into one file, but not minifying/uglifying; however, the bug persists.
Original (simplified) Code:
var myArray = ['item1', 'apple', 'item2', 'item3', 'bread', 'etc'];
//remove items from array according to some complex logic
for (var i = 0; i < myArray.length; i++) {
var cur = myArray[i];
if (cur.indexOf('item') === 0) { //if starts with "item" remove it
myArray.splice(i, 1);
i--; //*IMPORTANT* Move index back to avoid skipping next item
continue;
}
//do unrelated processing
}
//now use the array
alert(myArray);
Orig Code Output:
['apple', 'bread', 'etc']
This code works correctly as written. However, when i run it through r.js (with optimize: "none") it outputs this code which doesn't work as intended:
r.js output code:
var myArray = ['item1', 'apple', 'item2', 'item3', 'bread', 'etc'];
var _loop = function _loop(_i) {
var cur = myArray[_i];
if (cur.indexOf('item') === 0) {
myArray.splice(_i, 1);
_i--;
return 'continue';
}
}
for (var i = 0; i < myArray.length; i++) {
var _ret = _loop(i);
if (_ret === 'continue') continue;
}
alert(myArray);
r.js Code Output:
['apple', 'item3', 'bread', 'etc']
As you can see the code is nearly identical, but because the r.js moved my for loop into a function, I can't alter the index so after removing/splicing a value and moving back the index, that change to the index is lost entirely and the next item in the array is skipped.
Work Arounds
I could loop the index back to front so I don't need to alter the index, but I'm performing other calculations that need to be done in order (front-to-back). Plus I'm worried about this bug sneaking into my code in the future.
Questions
Is this how r.js is supposed to work (did i miss a simple setting)?
Am I coding in an anti-pattern and should change my approach?
Thanks!
Update
As discussed in the comments, the solution is to pull the declaration of i
out of the for
loop structure:
var i;
for (i = 0; i < myArray.length; i++) {
// ...
}
Opinion: Since there is no functional difference between the two ways of declaring i
, it's funny that they are interpreted as being different by r.js.
You could of course also go with Array.prototype.filter
to entirely dodge the problem:
var myArray = ['item1', 'apple', 'item2', 'item3', 'bread', 'etc'];
myArray = myArray.filter(item => ! /^item/.test(item));
console.log(myArray);
Performing additional logic could also be done both before and after filtering by using Array.prototype.forEach
:
var myArray = ['item1', 'apple', 'item2', 'item3', 'bread', 'etc'];
/** @return {boolean} */
function filterFn(item, index) {
console.log(`Doing additional stuff with: myArray[${index}] = ${item}`);
return ! /^item/.test(item);
}
myArray = myArray.filter(filterFn);
myArray.forEach((item, index) => {
console.log(`After: myArray[${index}] = ${item}`);
});