Javascript - Removing array item & DOM - javascript

I'm trying to learn Javascript and so I made a little code, but there is something wrong with it that I can
var array = new Array("Apple","Mangosteen","Durian","Pineapples");
...
...
function B() {
...
...
var BP = $("<p></p>");
BP.text("Click \"x\" on items to be removed").append("<br/>");
for (i=0;i<array.length;i++) {
var f = array[i];
var F = $("<div></div>");
F.attr({"class":"f"});
var N = $("<span></span>");
N.attr({"class":"n"});
N.text(f);
var d = $("<span></span>");
d.attr({"class":"cc sl"});
d.bind("click", function(e){
e.stopPropagation();
e.preventDefault();
IR(f,F);
});
d.html("×");
...
...
}
function IR(f,F) {
var a = array.indexOf(f);
array.splice(a,1);
F.remove();
}
When I added console.log(f); in function IR(), the value passed will always be "Pineapples", regardless if I'm clicking "x" on "Apples" or "Durian", the f value passed will always be "Pineapples". What is wrong with it?

You call a function inside a for loop - something you can read more about here JavaScript closure inside loops – simple practical example.
For now, if you're using ES6. The easiest way to solve it will be using let in the for loop.
for (let i=0;i<array.length;i++) { ...... }
Or else, use Array.forEach() instead of the for loop.
In your case it should be something like
array.forEach(function(fruit) {
var f = fruit;
var F = $("<div></div>");
F.attr({"class":"f"});
var N = $("<span></span>");
N.attr({"class":"n"});
N.text(c);
var d = $("<span></span>");
d.attr({"class":"cc sl"});
d.bind("click", function(e){
e.stopPropagation();
e.preventDefault();
IR(f,F);
});
d.html("×");
...
...
});

its because the scope of variable "i" is global, so var f = array[i]; will result in var f = array[3]; so you will get only "Pineapples".
I will give you a simple sample code to understand the issue. please run below code.
<script>
var funcs = [];
for (var i = 0; i < 3; i++) { // let's create 3 functions
funcs[i] = function() { // and store them in funcs
console.log("My value: " + i); // each should log its value.
};
}
for (var j = 0; j < 3; j++) {
funcs[j](); // and now let's run each one to see
}
</script>
you will get only 3 because "i" is in global scope.

When you declare a variable using var it will be declared within a scope of the function. That's why when you click 'x' javascript will pass to the IR function the last value of the variables - "Pineapples" and Pineapples's div. If you want to declare a variable within the 'for' cycle scope, use let. In this case, in every loop of 'for' cycle javascript will create a new variables f and F.
var array = new Array("Apple","Mangosteen","Durian","Pineapples");
function B() {
var BP = $("<div></div>");
BP.text("Click \"x\" on items to be removed").append("<br/>");
for (var i=0;i<array.length;i++) {
let f = array[i];
let F = $("<div></div>");
F.attr({"class":"f"});
var N = $("<span></span>");
N.attr({"class":"n"});
N.text(f);
var d = $("<span></span>");
d.attr({"class":"cc sl"});
d.bind("click", function(e){
e.stopPropagation();
e.preventDefault();
IR(f,F);
});
d.html("×");
F.append(N).append(d)
BP.append(F)
}
}
function IR(f,F) {
var a = array.indexOf(f);
array.splice(a,1);
F.remove();
}

Related

JSHint warning "Function declared within loop referencing an outer scope variable may lead to confusing semantics" . How can I improve the code?

JSHint shows the error:
"Function declared within loop referencing an outer scope variable may lead to confusing semantics".
How can I improve the following code to get rid of the warning?
var getPrecedence = function getPrecedence(operator, operators) {
var keys = Object.keys(Object(operators));
for (var i = 0, len = keys.length; i < len; i++) {
var check = Object.keys(operators[keys[i]]).some(function (item) {
return item === operator;
});
if (check) return operators[keys[i]][operator];
}
};
You are supposed not to use the function expression inside the loop body, but instead declare it outside:
function getPrecedence(operator, operators) {
function isOperator(item) {
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^
return item === operator;
}
var keys = Object.keys(Object(operators));
for (var i = 0, len = keys.length; i < len; i++) {
var check = Object.keys(operators[keys[i]]).some(isOperator);
// ^^^^^^^^^^
if (check) return operators[keys[i]][operator];
}
}
Of course the whole thing could be simplified by just using includes instead of some, and find instead of the loop:
function getPrecedence(operator, operators) {
var keys = Object.keys(Object(operators));
var opkey = keys.find(key =>
Object.keys(operators[key]).includes(operator)
);
if (opkey) return operators[opkey][operator];
}
And finally, Object.keys(…).includes(…) can be simplified to operator in operators[key].
Add it before calling the function, this one will bypass that check
/* jshint -W083 */

javascript loop through messages

I have 3 messages in variables.
var msg1 = "hello1";
var msg2 = "hello2";
var msg3 = "hello3";
I am trying to create a function that when i click it the first time it console.log(msg1), when i click it the second time it console.log(msg2), 3rd time console.log(msg3), 4th time console.log(msg1) and 5th msg2 etc.
$scope.clickMsg = function () {
console.log(msg1);
}
i've tried loops, timers etc but i could not make it work.
Does anyone know how to do this?
Use an array instead, and it's a bit easier, you'd just increment a number on each click, and use that number to select the item from the array
var msg = [
"hello1",
"hello2",
"hello3"
];
var i = 0;
var $scope = {};
$scope.clickMsg = function () {
console.log( msg[i] );
i++; // increment
if (i === msg.length) i = 0; // reset when end is reached
}
document.getElementById('test').addEventListener('click', $scope.clickMsg)
<button id="test">Click</button>
ES6 Generators based version:
var messages = (function*() {
for(;;) { yield msg1; yield msg2; yield msg3; }
})()
$scope.clickMsg = function () {
console.log(messages.next().value);
}
Unlike other answers, does not require you to use a different datatype and will also work for the locally scoped variables (i.e. non-window scoped variables).
Try It Online !
There are a few ways to do this in terms of accessing the string, I'd recommend putting them into an array rather than accessing the global/scoped object but it's up to you. Anyway on to the code.
var messagesArray = ["hello1", "hello2", "hello3"];
var messagesObject = {
msg1: "hello1",
msg2: "hello2",
msg3: "hello3"
}
var counter = 0;
function LogMessage() {
console.log(messagesArray[counter % 3]);
console.log(messagesObject["msg" + (counter % 3 + 1)]);
counter++
}
<button onclick="LogMessage()">Click Me</button>
Simply use with increment value like this
var msg1 = "hello1";
var msg2 = "hello2";
var msg3 = "hello3";
var c = 1;
$scope.clickMsg = function () {
c = c > 3 ? 1 : c;
console.log(window['msg'+c])
c++;
}
Working snippet
var msg1 = "hello1";
var msg2 = "hello2";
var msg3 = "hello3";
var c = 1;
var $scope={} //for testing
$scope.clickMsg = function () {
c = c > 3 ? 1 : c;
console.log(window['msg'+c])
c++;
}
function check(){ //for testing
$scope.clickMsg();
}
<button onclick="check()">click</button>
The alternative is using scopes, defining them as
this["msg"+i] = "some stuff";
and retrieving them as
this.msg0;
just do something like this, will work for you, make sure you reset it back if needed or do something, otherwise after first loop, you get undefined:
var msgs = ["hello1","hello2","hello3"], i=0;
$scope.clickMsg = function() { //angular $scope for example
console.log(msgs[i]);
if(i < msgs.length-1) {
i++;
} else {
i=0; //reset the loop
}
}

Closures in Loop not responding to the usual maneuvers

At first blush, this looks like the same old 'closures in a loop' problem, but applying my usual solution is not actually solving the problem. Here's the code:
G.MultiToggle = function(each){
//data takes the form
//[{"data":(see Toggle), "onOpen":function(){}, "onClosed":function(){}},...]
this.children = [];
var which = null;
var toggles = [];
var that = this;
function makeOpenFn(j){
var info = each[j];
console.log(j);
return function(){
console.log(j);
info["onOpen"]();
if(which!=null){
toggles[which].close_up();
}
which = j;
};
};
function makeClosedFn(j){
var info = each[j];
console.log(j);
return function(){
console.log(j);
info["onClosed"]();
which = null;
};
};
function makeToggler(obj,opener,closer) {
return new G.Toggle(
obj.data,
opener,
closer
);
};
for(var i=0; i<each.length; i++){
var openFn = makeOpenFn(i);
var closedFn = makeClosedFn(i);
toggles[i] = makeToggler(each[i],openFn,closedFn);
toggles[i].close_up();
that.addChild(toggles[i]);
}
console.log(toggles);
}
G.MultiToggle.prototype = new createjs.Container();
The openFn and closedFn are used as event handlers by the toggle object later on. When they're invoked, they all spit out the results from i=2. I've tried moving the info variable declaration into the inner functions, and many other gymnastic permutations. I'm pulling my hair out, over here. Any help would be appreciated.
EDIT: Added more of the surrounding code for context.
It's the old closure in a loop problem. Only, you've missed a variable:
for(var i=0; i<each.length; i++){
openFn = makeOpenFn(i); //------ looks OK
closedFn = makeClosedFn(i); //-- looks OK
toggles[i] = function(){
return new G.Toggle(
each[i].data, //--------- AHA! closure in a loop!
openFn,
closedFn
);
}();
toggles[i].close_up();
that.addChild(toggles[i]);
}
You just need to break the closure to that i as well:
toggles[i] = function(new_i){
return new G.Toggle(
each[new_i].data,
openFn,
closedFn
);
}(i);
Or, if you prefer the style of the other functions:
function makeToggler(obj,opener,closer) {
return new G.Toggle(
obj.data,
opener,
closer
);
}
for(var i=0; i<each.length; i++){
openFn = makeOpenFn(i); //------ looks OK
closedFn = makeClosedFn(i); //-- looks OK
toggles[i] = makeToggler(each[i],openFn,closedFn);
toggles[i].close_up();
that.addChild(toggles[i]);
}
If you think there is problem inside the loop, I'll show you that the problem is not from your loop.
Let's make a simple loop that same as your loop to proof it:
function my(a){
return a+3;
}
for (var i=0; i<3; i++){
var ab = my(i);
var aa = function(){
alert(ab);
alert(i);
}();
}

Why is running f[1]() different in Version B than in Version A, below?

Why is running f1 different in Version B than in Version A, below?
// Version A
var f = {};
for( var i=0 ; i<3 ; i++ ) {
f[i] = function() { alert(i); }
}
// Version B
var f = {};
for( var i=0 ; i<3 ; i++ ) {
f[i] = function(j) { return function() { alert(j); }; }(i);
}
The first code snippet makes 3 functions that are bound to i.
The second code snippet makes 3 functions that are closed around the value of i - a closure.
This means that in the first example, if you do:
i = "hi";
f[0](); // "hi"
Minor:
f should probably be initialized as f = [] since you're using it as an array.
This is because of a concept called as a closure where the values of the parameters of the function returning the function are preserved in the returned function

Generated functions keep reference to variable instead of value replacement?

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);
}
}() );
}

Categories