why this loop cannot stop? - javascript

I try to use jquery(jquery-2.1.1) and constructor function to build html. But when I tried this method in for loop, the loop can't stop. Can anyone tell me why it happens?
Here' s the code. Thanks a lot.
function tag() {
this.addClass = function(...theArgs) {
for (index in theArgs) {
this.$html.addClass(theArgs[index]);
}
}
this.setAttr = function(attr, value) {
this.$html.attr(attr, value);
}
this.append = function(...theArgs) {
for (index in theArgs) {
this.$html.append(theArgs[index]);
}
}
this.find = function(value) {
return this.$html.find(value);
}
this.empty = function() {
this.$html.empty();
}
this.remove = function(value) {
this.find(value).remove();
}
this.clone = function() {
return jQuery.extend(true, {}, this);
}
this.show = function() {
return this.$html[0];
}
}
function label(text) {
tag.call(this);
this.$html = $("<label></label>");
this.append(text);
}
for(var index = 0; index < 2; index++) {
var fieldLabel = new label(1);
console.log(index);
}

The problem here is you use the index (without var) as the running variable for all loops in your tag function. That index variable is still effective in the outer scope of the for-loop at the end (which should stop with the condition >=2).
At the beginning of the loop, index is 0. The next loop it should be 1. But when going into the inner append method, it's reset back to 0 due to the loop for-in (the argument passed in is just 1, so the spread notation makes an array of 1 length, and for-in stops with index set to 0) . So at the end of the second loop it is still 0. That means it will never become greater the value 1 (which is increased only at the beginning of the for-loop). The condition < 2 will always be satisfied and the for-loop just runs forever.
You can either use another name for the running variable in for-in. Or just declare another local-scoped index by using var, like this:
this.append = function(...theArgs) {
for (var index in theArgs) {
this.$html.append(theArgs[index]);
}
}
Or better using for-of as someone suggested.

Related

What is the proper way to look for undefined value

I have the following code:
if (array.indexOf("undefined")===-1){
do something...
}
My initial arrays is this:
array=[,,,,,,];
It gets filled with values as the program goes on but i want the function to stop when there are no undefined spaces. The above syntax though is not correct. Anybody can tell me why.
Your code looks for the string "undefined". You want to look for the first index containing an undefined value. The best way is to use findIndex:
if(array.findIndex(x=>x===undefined) !== -1) {
//do something
}
for(var ind in array) {
if(array[ind] === undefined) {
doSomething();
}
}
Your check didn't work because you passed the string "undefined" instead the the value itself. Also, .indexOf() is designed to explicitly ignore holes in the array.
It seems wasteful to use iteration to detect holes in the array. Instead you could just track how many holes have been filled by using a counter, and execute your code when the counter matches the length.
Either way, the proper way to detect a hole at a particular index is to use the in operator.
Here's a class-based solution for reuse:
class SpacesArray extends Array {
constructor(n, callback) {
super(n);
this.callback = callback;
this.counter = 0;
}
fillHole(n, val) {
if (!(n in this)) {
this.counter++;
console.log("filling hole", n);
}
this[n] = val;
if (this.counter === this.length) {
this.callback();
}
}
}
// Usage
var sa = new SpacesArray(10, function() {
console.log("DONE! " + this);
});
// Emulate delayed, random filling of each hole.
for (let i = 0; i < sa.length; i++) {
const ms = Math.floor(Math.random() * 5000);
setTimeout(() => sa.fillHole(i, i), ms);
}

The role of the (i) and (index) on this function

i have this code and this is working:
var wrap_inner = document.getElementsByClassName("wrapper"),
base_icon = document.getElementsByClassName("inner");
function fx_effect () {
for (var i = 0; i < wrap_inner.length; i++) {
(function (index) {
wrap_inner[i].addEventListener('click',function() {
base_icon[index].classList.toggle('tc');
});
})(i); <==== what is that? what do that?
}
}fx_effect();
but i don't know what is the index in: function (index) { ...
and what is (i) at the end of function.
Why index is used? What is its value?
why (i) at the end of function used like this?
When should be used in this way?
why the function like this don't work???:
function fx_effect () {
for (var i = 0; i < wrap_inner.length; i++) {
wrap_inner[i].addEventListener('click',function() {
base_icon[i].classList.toggle('tc');
});
}
}fx_effect();
I'm confused :(
The concept is javascript closures . First method is creating a closure ( a scope or a stack as analogy) for each value passed. So that when fx_effect is called, the inner method runs for values 0 to wran_inner.length distinctively as each value is in a different closure.
For second method, the scope will have just the final value, hence calling the method fx_effect will result in the loop running that method for final value of i, i.e. wrap_inner.length - 1

How does Javascript interpret this parameter error?

function each(collection, callback) {
if (Array.isArray(collection)) {
for (var i = 0; i < collection.length; i++) {
callback(collection[i]);
}
}
else {
for (var prop in collection) {
callback(collection[prop], prop, collection);
}
}
}
var array = [1, 2, 3, 4, 5];
function reduce(collection, callback, initial) {
var current = initial;
each(collection, function(current, e) {
current = callback(current, e);
})
return current;
}
console.log(reduce(array, function(a, b) { return a + b }, 0)); -->>> 0
I'm trying to rewrite the underscore each/reduce functions, and use the each function in reduce. I know that I have a mistake in there-- (current should not be in the each callback function) it should be just be
each(collection, function(e) {
current = callback(current, e);
})
and that returns 15 as it should, but why does it return 0 when you do add that current in there as a parameter? Shouldn't it just return NaN? As the last part of the loop will try to add 5 and undefined which is NaN.
The thing is that as soon as you add current to the parameter list of your callback function, you do have two variables - with the same name, but in different scopes. The one in the callback does shadow the one in the reduce function.
So your callback reducer is called with the element that each passed to your callback and undefined, but when assigning the result (NaN) to current it will just assign to the local variable of your each callback.
The outer current variable will stay completely unaffected, and when it is returned from reduce it still holds the initial value it was initialised with - 0.

function receives value as undefined

I have a timeout which calls a function until 100% progress is complete. It then executes the function I have assigned to it. Only the value that was given to it is undefined or at least part of it.
I'm not sure at which stage the code is losing the value being passed, thus making it return undefined but I have made a JS Fiddle with it in action for you to see it:
JS Fiddle
My end result is to receive the value correct then remove the given element like so:
function rmv_div(div_id) {
//div_id is not properly defined so cannot find the div.
document.getElementById('result').innerHTML = div_id;
var div = document.getElementById(div_id);
div.parentNode.removeChild(div);
}
The problem is that the variable i used inside func is created outside the scope of that function, and is increased at each iteration. Then, when you call func at the end, i equals array.length, so array[i] is undefined.
You can solve it creating another variable at each iteration that you won't increase:
Solution 1:
Demo: http://jsfiddle.net/qJ42h/4/ http://jsfiddle.net/qJ42h/11/
for (var i = 0; i < array.length; i++) {
var bar = document.getElementById('bar' + array[i]),
text = document.getElementById('text' + array[i]),
remove = 'wrap' + array[i],
j = i;
do_something(bar, text, function () {
rmv_div('id' + array[j]);
}, 1);
}
Solution 2
Demo: http://jsfiddle.net/qJ42h/8/ http://jsfiddle.net/qJ42h/12/
for (var i = 0; i < array.length; i++) {
var bar = document.getElementById('bar' + array[i]),
text = document.getElementById('text' + array[i]),
remove = 'wrap' + array[i];
do_something(bar, text, (function(i) {
return function(){ rmv_div('id' + array[i]); }
})(i), 1);
}
The problem here is that you didn't isolate the loop variable i inside the closure. However, this can be solved much more elegantly by using objects.
First off, I'm introducing the object that will encapsulate what you want; it gets initialized with a bar element and a function to call when it's done counting to 100. So, I'll call it BarCounter:
function BarCounter(element, fn)
{
this.element = element;
this.fn = fn;
this.text = element.getElementsByTagName('div')[0];
this.counter = 0;
}
This is just the constructor; it doesn't do anything useful; it resolves the text element, which is simply the first <div> tag it can find underneath the given element and stores that reference for later use.
Now we need a function that will do the work; let's call it run():
BarCounter.prototype.run = function()
{
var that = this;
if (this.counter < 100) {
this.text.innerHTML = this.counter++;
setTimeout(function() {
that.run();
}, 70);
} else {
this.fn(this.element);
}
}
The function will check whether the counter has reached 100 yet; until then it will update the text element with the current value, increase the counter and then call itself again after 70 msec. You can see how the reference to this is kept beforehand to retain the context in which the run() function is called later.
When all is done, it calls the completion function, passing in the element on which the BarCounter object operates.
The completion function is much easier if you pass the element to remove:
function removeDiv(element)
{
element.parentNode.removeChild(element);
}
The final step is to adjust the rest of your code:
var array = [1];
for (var i = 0; i < array.length; ++i) {
var bar = new BarCounter(
document.getElementById('bar' + array[i]),
removeDiv
);
bar.run();
}
It's very simple now; it creates a new BarCounter object and invokes its run() method. Done :)
Btw, you have the option to remove the element from within the object as well; this, of course, depends on your own needs.
Demo

Difference between closures in loops in JavaScript

What is the difference between these functions? Why does the first one work and the second does not work as expected?
http://jsfiddle.net/GKDev/x6pyg/ (this works)
http://jsfiddle.net/GKDev/bv4em/ (and this is not)
I'm trying to loop over input elements and add onfocus events on them:
for (var i = 0; i < helpText.length; i++) {
var item = helpText[i];
document.getElementById(item.id).onfocus = function() {
showHelp(item.help);
};
}
In your non-working example, when the anonymous function is called, item has the last value it held when the for loop finished executing. That's because this variable belongs to the parent function that contains the for loop.
In your working example, you create a new function, pass in the current value of item like so:
function (help) {
return function () {
showHelp(help); // <-- This will be the value enclosed in this anonymous function
};
}(item.help); // <-- Calls an anonymous function passing in the current value
This creates a new closure around that value as it existed during that iteration. When the anonymous function is called, it uses that local value.
It is treated as:
var item;
for (var i = 0; i < helpText.length; i++) {
item = helpText[i];
document.getElementById(item.id).onfocus = function() {
showHelp(item.help);
};
}
The loop has finished before any focus callback fires and at that point item is the last item assigned.
It is pretty easy in fact.
In the first case you are passing item.help inside of the closure, which acts as a local copy. It's like a prisoner of its scope. What happens outside, nobody cares.
for (var i = 0; i < helpText.length; i++) {
var item = helpText[i];
document.getElementById(item.id).onfocus = function (help) {
return function () {
showHelp(help);
};
}(item.help);
}
In the second one, there is no closure preserving the value of item, which means that when item is evaluated, it evaluates to its actual value, i.e.: the last element of the array, since the last loop of the for set its value to the last value of the array.
for (var i = 0; i < helpText.length; i++) {
var item = helpText[i];
document.getElementById(item.id).onfocus = function() {
showHelp(item.help);
}
}

Categories