JS Hint - don't make functions within a loop - javascript

I can not get around JSHint's error message. Here is the loop I am using:
for (i = 0; i < Collection.length; i += 4) {
data.push({
items : Collection.slice(i, i + 4).map(function(item) {
return {
id: item[0],
title: item[1],
};
})
});
}

You can just move the function outside the loop and pass a reference to it to map:
function mapCallback(item) {
return {
id : item[0],
title : item[1],
};
}
for (i = 0; i < Collection.length; i += 4) {
data.push({
items: Collection.slice(i, i + 4).map(mapCallback)
});
}
Alternatively, you can use a JSHint directive to ignore function expressions inside loops. Just put this at the top of the file in question:
/*jshint loopfunc: true */

Declaring a function in a loop is messy, and potentially error prone. Instead, define the function once, and then enter the loop.
var objMaker = function(item) {
return {
id : item[0],
title : item[1],
};
};
for (i = 0; i < Collection.length; i += 4) {
data.push({
items : Collection.slice(i, i + 4).map(objMaker)
});
}

People say "Declaring a function in a loop is messy and potentially error-prone", but functions within loops is what directly instructed in, for example, Array.prototype.forEach method. Just because the word "function" should theoretically mean defining it anew in every forEach call it does not mean it is actually defined each time by the Javascript engine.
The same goes for outer loops since engines have "lazy" processing of instructions. They are not going to redefine the whole forEach/Map/etc construct-instruction anew if nothing really changed about it, they will just feed new arguments to it.
The times of ancient JS engines which were clueless about such simple things as well as of code context are long gone. And yet we are getting this ancient warning which was conceived when functions were not yet capable of being passed as arguments as in the cases of forEach or Map.

Related

What is the JavaScript equivalent to Ruby's Enumerable#cycle?

In Ruby, there's a method called, Enumerable#cycle, which allows for repeated loops by x number of times in a collection. I'm looking for something similar in JavaScript but could not find an equivalent.
Does anyone know of a JavaScript equivalent to Ruby's, Enumerable#cycle?
Context: I am trying to loop over the same array in JavaScript -- twice. Once iteration reaches the end, I would like the iteration to start from the beginning of the array and eventually stop. The stopping part is not hard -- it's the cycling of the iteration that I'm trying to achieve in JavaScript. I've been able to do this in Ruby, however, with said method.
I think there is nothing inbuilt which is equivalent to this but if you need to you can follow this post for more detail:
Javascript call a function several times with the arguments.
Apart from the options provided there, you could also extend the Array.prototype to enable this method:
Array.prototype.cycle = function (n, callback) {
for (var i = 0; i < n; i++) {
this.forEach(callback);
}
}
and, could use this like following:
a = [1, 2, 3, 4]
a.cycle(2, function (i) { console.log(i); });
// this will print all the values two times
There is no JavaScript built in (or even proposed to my knowledge) that can do this. However, it's not too hard to accomplish yourself.
Let's assume for a minute that your talking about Arrays, and let's waive away discussion about modifying prototypes or not. The simple version of what you want could be:
Array.prototype.cycle = function(cycleCount, callback) {
for(var i = 0; i < cycleCount; i++) {
this = this.map(callback);
}
}
Let's assume you have an array of numbers. Then you could call this by doing:
myNumberArray.cycle(2, function(num, i) {
return num * 2;
});
We could even get fancy, and let you specify a different action for each cycle:
Array.prototype.cycle = function(cycleCount, callback) {
if(Object.prototype.toString.call( callback ) === '[object Array]') {
if(callback.length === cycleCount) {
for(var i = 0; i < cycleCount; i++) {
this = this.map(callback[i]);
}
} else {
// Uhoh, we don't have the right number of callbacks
throw new Error('If using multiple callbacks, the umber of callback\'s must match the number of cycles');
}
} else {
for(var i = 0; i < cycleCount; i++) {
this = this.map(callback);
}
}
}
There is more error checking you would need to do to make that robust, but you get the idea. :)

Uncaught ReferenceError: i is not defined

I'm trying to make a for-loop base on my array
var lists = [ "a", "b", "c", "d" ];
JS
for ( i = 0; i < lists.length; i++) {
// console.log(lists[i]);
$(".sa-report-btn-"+lists[i] ).click(function () {
$(".sa-hide-"+lists[i]).removeClass("hidden");
$(".sa-report-"+lists[i]).addClass("hidden");
});
$(".sa-hide-btn-"+lists[i]).click(function () {
$(".sa-hide-"+lists[i]).addClass("hidden");
$(".sa-report-"+lists[i]).removeClass("hidden");
});
}
Am I doing it correctly ? I got Uncaught ReferenceError: i is not defined
Can I concat each loop with my jQuery selector like this --> $(".sa-hide-"+lists[i]) ? just curious ...
First off, it sounds like you're using strict mode — good! It's saved you from falling prey to The Horror of Implicit Globals.
There are two issues with the code.
The first one is that you're missing the declaration for i. You need to add var i; above the loop, e.g:
var i;
for ( i = 0; i < lists.length; i++) {
// ...
or
for (var i = 0; i < lists.length; i++) {
Note, though, that even in that latter example, the i variable is function-wide, not limited to the for loop.
The second one is more subtle, and is outlined in this question and its answers: Your click handlers will have an enduring reference to the i variable, not a copy of it as of where they were created. So when they run in response to a click, they'll see i as the value lists.length (the value it has when the loop has finished).
In your case, it's really easy to fix (and you don't have to declare i anymore): Remove the loop entirely, and replace it with Array#forEach or jQuery.each:
lists.forEach(function(list) {
$(".sa-report-btn-" + list).click(function () {
$(".sa-hide-" + list).removeClass("hidden");
$(".sa-report-" + list).addClass("hidden");
});
$(".sa-hide-btn-" + list).click(function () {
$(".sa-hide-" + list).addClass("hidden");
$(".sa-report-" + list).removeClass("hidden");
});
});
If you need to support really old browsers, you can either shim Array#forEach (which was added in 2009, as part of ECMAScript5), or you can use $.each (jQuery.each) instead:
$.each(lists, function(index, list) {
// Note addition ------^
$(".sa-report-btn-" + list).click(function () {
$(".sa-hide-" + list).removeClass("hidden");
$(".sa-report-" + list).addClass("hidden");
});
$(".sa-hide-btn-" + list).click(function () {
$(".sa-hide-" + list).addClass("hidden");
$(".sa-report-" + list).removeClass("hidden");
});
});
Note that we don't actually use index anywhere in our callback, but we have to specify it because $.each calls our callback with the index as the first argument, and the value as the second. (Which is why I prefer Array#forEach.) So we have to accept two arguments, with the one we want being the second one.

Issue with Javascript For loop

Consider the Code below:
function splicer()
{
var arrayElements = ["elem1","elem2","elem3","elem4"];
for(var index in arrayElements)
{
arrayElements.splice(index,1);
}
alert("Elements: "+arrayElements);
}
The above function is supposed to remove all the elements from the array "arrayElements". But it won't.
Javascript engine maintains the "index" as it is and doesn't mind the array being modified.
People might expect something like "for each" loop that doesn't have this kind of issue
even the following code doesn't seem to work:
function splicer()
{
...
for(var index in arrayElements)
{
arrayElements.splice(index--,1);
}
...
}
even when changing the value of the variable "index" doesn't seem to work.
the changed value is available inside the "for(...){...}" block but, as the loop reaches the next iteration, the value gets reset and continues from the next index as clockwork.
so it seems code like this might be the only solution:
function splicer()
{
var arrayElements = ["elem1","elem2","elem3","elem4"];
for(var index=0;index<arrayElements.length;index++)
{
arrayElements.splice(index--,1);
}
alert("Elements: "+arrayElements);
}
Tested in: Firefox 16 Beta.
But placing a unary Operator inside a "splice()" method seems to be misleading at first sight.
This might be worth considering to the "W3C" or whomever it may concern so that they come up with a nice solution.
You may want to refer to John Resig's array.remove() link.
// Array Remove - By John Resig (MIT Licensed)
Array.prototype.remove = function(from, to) {
var rest = this.slice((to || from) + 1 || this.length);
this.length = from < 0 ? this.length + from : from;
return this.push.apply(this, rest);
};
Try this:
*Splice modifies the original array, hence tge loop skips the alternate values. *
var arrayElements = ["elem1","elem2","elem3","elem4"];
arrayElements.splice(0,arrayElements.length);
alert("Elements: "+arrayElements)

turn array into function, making a javascript loader with jquery

how can we make a function from arrays like
$.loader({
js: [
['1.js','3.js','2.js'],
['4.js'],
['5.js']
]
});
into something that does
$.when(
$.getScript("1.js"),
$.getScript('3.js'),
$.getScript('2.js'))
.then(
function(){
$.getScript('4.js').then(function(){
$.getScript('5.js');
})
});
heres what currently im working on
loader: function(arg){
var js = arg;
var phase = 0;
for (var i = 0; i < o.length; i++) {
console.log(o[i]);
$.each(o[i], function(key,src) {
//append javascript
});
};
}
the problem is i dont know where to start.
heres what i have in mind
i divide them into phases.
set phase 0, it will run the fist array but how can i do it ? $.getScript(src) for each src dosent stack. or chain. maybe put it one var sting ? like string + = $.getScript(src). so it will be like $.getScript("1.js").getScript("3.js").getScript("2.js") until there is arrays of string which is 3 in this example. then do something like append $.when( infront of the string then at the back of the string we put .then(function(){ until it finish then somehow closed the string.
ok i dont know ( and here goes the post at stackoverflow )
function lScripts(startAt){
for (var i = startAt; i < js.length; i++) {
if (js[i].constructor == Array){
for (var j = 0; j < js[i].length; j++) {
if(j==js[i].length){
$.getScript('js[i][j]',function(){
lScripts(startAt+1);
});
}else{
$.getScript('js[i][j]');
}
}
}else{
$.getScript('js[i]',function(){
lScripts(startAt+1);
});
}
}
}
lScripts(0);
Just to be clear, are you talking about writing a solution yourself that loads up a bunch of scripts? If so, dont do it yourself. Stand on someone else's shoulders.
RequireJS already does what you're asking.
http://requirejs.org/
It even has documentation for use with jQuery.
http://requirejs.org/docs/jquery.html
Someone else already did the hard work and wrote and tested a good product. Save yourself time and stress.
I hope this is what you mean: http://jsfiddle.net/pimvdb/hdN3Q/1/.
(function($) {
function load(files, index) {
var arr = [];
// fill arr with getScript requests (fake here)
for(var i = 0; i < files[index].length; i++) {
arr.push(console.log(files[index][i]));
}
// call $.when with arr as array of arguments (using apply)
$.when.apply(null, arr).then(function() {
if(index < files.length - 1) {
// if we have to move to the next chunk,
// load that next one
setTimeout(function() {
// timeout is to show they get processed in chunks
load(files, index + 1);
}, 1000);
}
});
}
$.loader = function(obj) {
load(obj.js, 0);
}
})(jQuery);
$.loader({
js: [
['1.js','3.js','2.js'],
['4.js'],
['5.js']
]
});
.apply essentially does the same thing as calling a function. However, because the amount of arguments to $.when differs depending on the input, you can't just call $.when(...) because you don't have a fixed number of arguments. The way to call a function with a variable amount of arguments is using .apply. It works like this:
someFunction(1, 2, 3);
is equal to:
someFunction.apply(null, [1, 2, 3]);
(The null refers to the execution context which is out of scope here.) The great thing about this is that you can build an array of any size. So you can call the function with any variable amount of arguments this way.
In the load function, arr gets filled with getScript functions, and it works the same way:
var arr = [getScript('file1'), getScript('file2')];
$.when.apply(null, arr);
is equal to:
$.when(getScript('file1'), getScript('file2'));
In load, we fill arr with the files of a chunk, e.g. in your case the first chunk is 1.js, 3.js and 2.js. This is done using a for loop but the array you will end up with you can just pass to .apply.
When all files are loaded, .then is called. The function we pass there should load the next chunk, but only if there is a next chunk. If there are 3 chunks, it should go to the next one when index is 0 or 1. When chunk 2 has finished, there is no next chunk so it should not continue to the next one as there isn't a next one.
So we need this if clause:
if(index < files.length - 1) {
Say files.length is 3. Then this if conditional will only pass when index is 0 or 1, which is what we want.

JSLint Error Report - Whats wrong with this?

I got this error and dont know what could be the cause. Any idea?
Problem at line 2127 character 18: Bad for in variable 'sport'.
for (sport in sugested_sports)
// make array
var sugested_sports = data.split(",");
// pre build DIV
var sporty_items = '';
for (sport in sugested_sports)
{
if (sugested_sports.hasOwnProperty(sport)) {
sporty_items += ''+sugested_sports[sport]+'';
}
}
// insert DIV
DIVsuggestions.html(sporty_items);
thx alot.
Try
var sport;
for (sport in sugested_sports)
This takes care of the missing variable declaration and places it outside the for loop ( see jsLint error "Cannot set property 'first' of undefined" ).
Pointy's answer is probably the one that lint is complaining about.
As a general rule you though, you should be careful when using for (... in ...). People often confuse this construct with foreach from C#, or other similar concepts in other languages, when in fact it isn't related. The javascript for in construct iterates every member of the object -- rather than just values in a collection -- including methods and properties. This behaviour can often lead to unexpected side effects if you aren't aware of how it works beforehand.
For example:
x = ['one', 'two'];
for (var value in x) {
alert(value);
}
That yields two alerts, the first contaning 0 and the second 1, notably the indexes of the collection.
If we change that up a bit:
x = ['one', 'two'];
x.method = function() {};
for (var value in x) {
alert(value);
}
We end up with three alerts this time, 0, 1, and method. This is the unexpected behaviour I was referring to. It's fine to use in if you know what it does, but I've seen it catch people out on more than one occasion.
The following works with both examples:
x = ['one', 'two'];
for (var i = 0; i < x.length; i++) {
alert(i);
}
All the error means in JSHint/JSLint is that you didn't declare your key/iterator variable. As #Christopher suggests, JSLint wants you to declare it at the top of its scope (google JavaScript hoisting for more on hoisting, like this link):
/*global data, identifier, DIVsuggestions */
// We'll pretend all of the above were passed in from a function's parameters
// by using JSLint's "global" keyword -- now you can paste this code into
// jslint.com and have it pass muster.
// make array
var sugested_sports = data.split(","),
sporty_items = '', // pre build DIV
sport; // <<<< **** DECLARE YOUR "KEY" HERE ****
for (sport in sugested_sports)
{
if (sugested_sports.hasOwnProperty(sport)) {
sporty_items += '<a href="#'+identifier[1]+'">'
+sugested_sports[sport]+'</a>';
}
}
// insert DIV
DIVsuggestions.html(sporty_items);
This bad for in variable error here reduces to the same as a 'sport' was used before it was defined error elsewhere.
EDIT: It's worth mentioning that if your for is in an internal function, you need to declare your for in variable in that same context. JSLint will complain if you declare the for in in the parent context.
Example:
function spam(d)
{
var fnTest, row; // `row` is defined "too early"
fnTest = function (data) {
for (row in data)
{
if (data.hasOwnProperty(row))
{
console.log(data.row);
}
}
};
fnTest(d);
}
To make things happy, move row into the internal function. Even though it was technically still in scope, JSLint doesn't like the "superscope" that was used before.
function spam(d)
{
var fnTest;
fnTest = function (data) {
var row; // and JSLint is happy! ;^D
for (row in data)
{
if (data.hasOwnProperty(row))
{
console.log(data.row);
}
}
};
fnTest(d);
}
By the way, James' concern is covered by the hasOwnProperty check the OP has inserted. Take out that check, and JSLint will complain, "The body of a for in should be wrapped in an if statement to filter unwanted properties from the prototype". Here's a little more on hasOwnProperty with for... in, if you're interested.
var sugested_sports = data.split(","),
sport,
sport_items = '';
for (sport in sugested_sports)
{
//
}

Categories

Resources