Anonymous functions, scope and variables - javascript

function UpgradeShop(){
var selectedIndex;
var category_btns = [];
for(var i = 0; i < 5; i++){
category_btns[i] = new Button(38, 68 + i * 75, 284, 70);
category_btns[i].action = function() {
selectedIndex = i; // ?
draw_ui(); // ?
};
}
this.draw_ui = function(){
...
}
}
I have a custom class named Button and I want to create 5 of them. I gave them each an action variable that executes when a click is detected.
The variable selectedIndex as well as the method draw_ui are found in the class that I'm declaring these functions in, not the Button class. I noticed that simply calling draw_ui() cannot be found and this.draw_ui() tries to find the method within the Button class. How do I assert that the function calls and variable assignments get directed towards the defining class?

For the first part of your question see this answer: JavaScript closure inside loops – simple practical example as this is a very commonly asked question which has been asked a lot of times (not always easy to find with a search though if you don't know what to search for).
For the second part of your question you need to save the value of this in a local variable like this:
function UpgradeShop(){
var selectedIndex;
var category_btns = [];
var self = this;
for(var i = 0; i < 5; i++){
(function(index) {
category_btns[index] = new Button(38, 68 + i * 75, 284, 70);
category_btns[index].action = function() {
selectedIndex = index;
self.draw_ui(); // use self here
};
})(i);
}
this.draw_ui = function(){
...
}
}

Related

Best practice: functions within loops

I got the following code which works perfectly. What it does is: in a table it highlights the corresponding table header cell and table first column cell when you hover over any table cell.
// Row & Column Highlight
(function() {
var gridCellRow = null,
gridCellCol = null,
tableElement = document.getElementsByClassName('inner_table');
for (var i = 0, len_i = tableElement.length; i < len_i; i++) {
if (tableElement[i].getElementsByClassName('row_label_cell').length > 0) {
var gridCell = tableElement[i].getElementsByClassName('input_cell');
for (var j = 0, len_j = gridCell.length; j < len_j; j++) {
function gridCellParents(currentCell) {
return gridCellRow = currentCell.parentNode.firstElementChild,
gridCellCol = currentCell.parentNode.parentNode.rows[0].cells[currentCell.cellIndex];
}
gridCell[j].addEventListener('mouseover', (function() {
gridCellParents(this);
gridCellRow.classList.add('highlight');
gridCellCol.classList.add('highlight');
}));
gridCell[j].addEventListener('mouseout', (function() {
gridCellRow.classList.remove('highlight');
gridCellCol.classList.remove('highlight');
}));
}
}
}
}());
However, JSHint tells me, that
for (var j = 0, len_j = gridCell.length; j < len_j; j++) {
function gridCellParents(currentCell) {
return gridCellRow = currentCell.parentNode.firstElementChild,
gridCellCol = currentCell.parentNode.parentNode.rows[0].cells[currentCell.cellIndex];
}
is not best practice "Function declarations should not be placed in blocks. Use a function expression or move the statement to the top of the outer function."
as well as
gridCell[j].addEventListener('mouseover', (function() {
gridCellParents(this);
gridCellRow.classList.add('highlight');
gridCellCol.classList.add('highlight');
}));
gridCell[j].addEventListener('mouseout', (function() {
gridCellRow.classList.remove('highlight');
gridCellCol.classList.remove('highlight');
}));
}
is not best practice "Don't make functions within a loop."
So how am I correctly and according to best practice building this whole function?
Function deceleration shouldn't be within loops because it makes no
sense to re-create the same function over and over again, in a
"continuous flow" (unlike other situation where the same function
might be created again, in a more complex code). The main reason is
because of hoisting and it strongly goes against javascript
principles to write functions declarations inside loops.
A good starting point, with a more ordered code:
// Row & Column Highlight
(function() {
var gridCellRow,
gridCellCol,
gridCell,
tableElement = document.getElementsByClassName('inner_table');
function gridCellParents(currentCell) {
gridCellRow = currentCell.parentNode.firstElementChild,
gridCellCol = currentCell.parentNode.parentNode.rows[0].cells[currentCell.cellIndex];
}
function onMouseEnter() {
gridCellParents(this);
gridCellRow.classList.add('highlight');
gridCellCol.classList.add('highlight');
}
function onMuoseLeave() {
gridCellRow.classList.remove('highlight');
gridCellCol.classList.remove('highlight');
}
for (var i = 0, len_i = tableElement.length; i < len_i; i++) {
if (tableElement[i].getElementsByClassName('row_label_cell').length > 0) {
gridCell = tableElement[i].getElementsByClassName('input_cell');
for (var j = 0, len_j = gridCell.length; j < len_j; j++) {
gridCell[j].addEventListener('mouseenter', onMouseEnter);
gridCell[j].addEventListener('mouseleave', onMuoseLeave);
}
}
}}());
As you can see, I've modified your events to mousenter and mouseleave which might better suit your needs and be better for overall performance.
Update - delegated version:
// Row & Column Highlight
(function() {
var gridCell,
tableElement = document.querySelectorAll('.inner_table');
function getCellParents(cell){
return {
row : cell.parentNode.firstElementChild, // row
col : cell.parentNode.parentNode.rows[0].cells[cell.cellIndex] // col
};
}
function updateGridCellParents(cell, state) {
state = state ? 'add' : 'remove';
var parents = getCellParents(cell);
parents.row.classList[state]('highlight');
parents.col.classList[state]('highlight');
}
funciton checkTarget(target){
// make sure the element is what we expected it to be
return target.className.indexOf('input_cell') != 0;
}
function onMouseEvents(e){
checkTarget(e.target) && updateGridCellParents(e.target, e.type == "mouseover");
}
document.body.addEventListener('mouseover', onMouseEvents);
document.body.addEventListener('mouseout', onMouseEvents);
})();
In addition to the previous answer, I think it is important to also state why it is a bad practice.
The issue when creating functions inside loops is that they often use values that depends on the loop's iteration. Let's have an example.
// Create three function, that writes their number
var funcs = [];
for(var i=0; i<3; i++){
funcs.push(function(){
document.write(i);
});
}
// Call them.
funcs.forEach(function(f){
f();
});
One may expect the above code to write 1 then 2 then 3. However, because variables in JS are not block-scoped but function-scoped (except for the new let and const), the closure of all three of these functions will actually use the exact same i: 3, the last value it had been given (and thus the value it still has).
Because of this behaviour, this is very easy to make mistakes. Hence, it is not recommended.
If you need to create a function that depends of the value of a loop, you can use a factory.
// Create a factory function that returns a
// function that writes the argument.
function writerFactory(msg){
return function(){
document.write(msg);
}
}
// Create three functions, that write their number.
var funcs = [];
for(var i=0; i<3; i++){
funcs.push(writerFactory(i));
}
// Call them.
funcs.forEach(function(f){
f();
});
This time, each function has a different closure: the one that is created by each call of the factory. They all have access to a different msg.

Multiple Timers with setTimeInterval

I am facing a problem with setInterval being used in a loop.
I have a function subscribeFeed( ) which takes an array of urls as input.
It loops through the url array and subscribes each url to getFeedAutomatically() using a setInterval function.
so if three URL's are there in the array, then 3 setInterval's will be called.
The problem is
1)how to distinguish which setInterval is called for which URL.
2)it is causing Runtime exception in setInterval( i guess because of closure problem in javascript)
//constructor
function myfeed(){
this.feedArray = [];
}
myfeed.prototype.constructor= myfeed;
myfeed.prototype.subscribeFeed =function(feedUrl){
var i=0;
var url;
var count = 0;
var _this = this;
var feedInfo = {
url : [],
status : ""
};
var urlinfo = [];
feedUrl = (feedUrl instanceof Array) ? feedUrl : [feedUrl];
//notifyInterval = (notifyInterval instanceof Array) ? notifyInterval: [notifyInterval];
for (i = 0; i < feedUrl.length; i++) {
urlinfo[i] = {
url:'',
notifyInterval:5000,// Default Notify/Refresh interval for the feed
isenable:true, // true allows the feed to be fetched from the URL
timerID: null, //default ID is null
called : false,
position : 0,
getFeedAutomatically : function(url){
_this.getFeedUpdate(url);
},
};
urlinfo[i].url = feedUrl[i].URL;
//overide the default notify interval
if(feedUrl[i].NotifyInterval /*&& (feedUrl[i] !=undefined)*/){
urlinfo[i].notifyInterval = feedUrl[i].NotifyInterval;
}
// Trigger the Feed registered event with the info about URL and status
feedInfo.url[i] = feedUrl[i].URL;
//Set the interval to get the feed.
urlinfo[i].timerID = setInterval(function(){
urlinfo[i].getFeedAutomatically(urlinfo[i].url);
}, urlinfo[i].notifyInterval);
this.feedArray.push(urlinfo[i]);
}
}
// The getFeedUpate function will make an Ajax request and coninue
myfeed.prototype.getFeedUpdate = function( ){
}
I am posting the same on jsfiddle
http://jsfiddle.net/visibleinvisibly/S37Rj/
Thanking you in advance
After some prototyping i found a answer ,which has the answer,move the closure outside
function myclass(){
}
myclass.prototype.funone= function(){
var counter =0;
var timerID;
timerID = setInterval( function(){
alert(counter++);
},1000);
}
myclass.prototype.funtwo= function(){
var timerID2;
var counter2 =50;
timerID2 = setInterval( function(){
alert(counter2++);
},2000);
}
myclass.prototype.funthree = function( ){
var urlArray =["google.com","yahoo.com"];
var timeArray =[15000,6000];
var timerID ;
for(var i=0;i<2; i++){
var url = urlArray[i];
var timerinterval = timeArray[i];
timerID = this.register( url,timerinterval);
}
}
myclass.prototype.register = function(url,timerInterval){
var myUrl =url;
var myTimer = timerInterval;
var timerID = setInterval( function(){
alert(myUrl+"with"+ myTimer);
},myTimer);
}
var m = new myclass( );
m.funthree( );
http://jsfiddle.net/visibleinvisibly/Q4SBG/13/
The move the index binding from the setInterval and pass the url and time interval.
It works perfectly
You might want to have a look at this answer (under "The this variable" at the bottom) about what the this value means.
The error in your code may have something to do with using a counter in a loop and creating closures depending on the counter. The simplest way to create such closures is.
for(i=0;i<len;i++){
object.myCallback = (function(counter){
return function(){
doSomethingWith(counter);
}
}(i));
}
When creating closures on the fly like that you should be careful not dragging large or large amounts of variables into the closure scope. The link above and code below shows how to do this safely.
I've changed some of the code to make it simpler and not copy stuff that doesn't need to be copied, the setInterval is setTimeout so it only does it once but it's the same idea.
//constructor
function MyFeed(){
this.feedArray = [];
}
MyFeed.prototype.subscribeFeed =function(feedUrl){
var i=0,urlInfo=[];
feedUrl = (feedUrl instanceof Array) ? feedUrl : [feedUrl];
for (i = 0; i < feedUrl.length; i++) {
feedUrl[i].isEnable=true;
feedUrl[i].called=false;
feedUrl[i].position=0;//not sure what this is supposed to do
//Set the interval to get the feed.
feedUrl[i].timerID = setTimeout(this.closures//changed this to timeout
.getFeedUpdate(this)
,feedUrl[i].notifyInterval||100//changed default value
);
this.feedArray.push(feedUrl[i]);
}
};
// The getFeedUpate function will make an Ajax request and coninue
MyFeed.prototype.getFeedUpdate = function( index ){
console.log("in getFeedUpdate, this is now:",this);
console.log("my feed url object:",this.feedArray[index].url);
};
//limit closure scope, define closure creators here
MyFeed.prototype.closures={
//this.closures.getFeedUpdate(this)
// will return a closure that calls this.getFeedUpdate
// with correct parameters
getFeedUpdate:function(me){
var index = me.feedArray.length;
return function(){
me.getFeedUpdate(index);
};
}
};
//code to test adding single feed
var mf = new MyFeed();
mf.subscribeFeed({
url:"I am last",
notifyInterval:1000
});
//add another single feed
mf.subscribeFeed({
url:"first.com"
});
//and another
mf.subscribeFeed({
url:"second.com"
});
//and add some more feeds in an array of feeds
mf.subscribeFeed([
{
url:"third"
},
{
url:"fifth"
},
{
url:"no, I am last",
notifyInterval:1500
}
]);
Try FireFox with the FireBug plugin or Chrome and press F12 to see the console, when the log statements log something you can click on it to see the details of the logged item. Very helpful to log objects like this or simple values like index

Javascript onchange select binding issue

I cannot seems to select the correct keywords in google to find this answer so...
I am creating a series of functions within a class to handle select box changes. Here is a very simple example of how the javascript is laid out
function example(n) {
this.name = n;
}
example.prototype.setChange(i) {
var c = document.getElementById('test' + i );
for ( var x = 0; x < 10; x++
c.options[0] = new Option(x, x);
c.change = this.doChange;
}
example.prototype.doChange() {
alert(this.name);
}
Everything works find until I try to access 'this'. When doChange is called, this is the actual select html object instead of the class. Because this is an event, I cannot use .call(this) to enforce the proper bindings.
You loose your this by doing this.doChange. Use something like
function example(n) {
this.name = n;
var self = this;
this.doChange = function () {
alert(self.name);
}
}

Javascript reference in loop: "Uncaught TypeError: Cannot read property 'value' of undefined"

I tried debugging my code for like a few hour but I got nothing out of it. The issue is that it makes absolutely no sense on why it reports an error every time I tried to use document.forms[0][i] (i as the iterator) in the event listener but "this" satisfies the code.
//broken
var addListeners = function() {
var i;
var formFields = document.forms[0];
var formSubmit = formFields["submit"];
for (i = 0; i < formFields.length; i++) {
if (formFields[i] != formSubmit) {
formFields[i].onblur = (function () {
checkNonEmpty(formFields[i]);
});
}
}
};
//works
var addListeners = function() {
var i;
var formFields = document.forms[0];
var formSubmit = formFields["submit"];
for (i = 0; i < formFields.length; i++) {
if (formFields[i] != formSubmit) {
formFields[i].onblur = (function () {
checkNonEmpty(this);
});
}
}
};
Wouldn't "this" refer to document.forms[0][i]?... formFields references to document.forms[0]. However the exact same code (with "this" where formFields[i] is at) works just fine.
Here is the demo: http://jsfiddle.net/PbHwy/
Cranio's answer already contains the root of the matter. To get rid of this you can either include formFields[i] by using closures
var blurCallbackGenerator = function(element){
return function () {
checkNonEmpty(element);
};
};
formFields[i].onblur = blurCallbackGenerator(formFields[i]);
/* // dense version:
formFields[i].onblur = (function(element){
return function () {
checkNonEmpty(element);
};
})(formFields[i]);
*/
or simply using this.
See also:
MDN: Creating closures in loops: A common mistake
Because you define formFields in a scope outside (or better, different than) the event listener. When the event listener is called, it is called not in the addListeners function where you define formFields, but "independently", so the reference is lost and its value is undefined (but this works because it is not dependent on that scope).
The problem is that the variable i (referred to in each of your handlers) is the exact same variable in each of them, which by the time the loop has finished has value formFields.length+1 and is therefore wrong for all of them. Try this instead [note: the below used to say something VERY WRONG before I edited it -- thanks to Zeta for pointing out my mistake]:
var addListeners = function() {
var i;
var formFields = document.forms[0];
var formSubmit = formFields["submit"];
for (i = 0; i < formFields.length; i++) {
if (formFields[i] != formSubmit) {
formFields[i].onblur = (function(j) {
return (function () {
checkNonEmpty(formFields[j]);
})(i);
});
}
}
};
and you'll find it works (unless there's another bug that I haven't noticed).
If you can afford to support only Javascript 1.7 and above, you can instead write your old code but make your for look like this: for (let i=0; i<formFields.length; i++). But you quite possibly can't.

JavaScript closures and variable scope

I am having trouble with JS closures:
// arg: an array of strings. each string is a mentioned user.
// fills in the list of mentioned users. Click on a mentioned user's name causes the page to load that user's info.
function fillInMentioned(mentions) {
var mentionList = document.getElementById("mention-list");
mentionList.innerHTML = "";
for (var i = 0; i < mentions.length; i++) {
var newAnchor = document.createElement("a");
// cause the page to load info for this screen name
newAnchor.onclick = function () { loadUsernameInfo(mentions[i]) };
// give this anchor the necessary content
newAnchor.innerHTML = mentions[i];
var newListItem = document.createElement("li");
newListItem.appendChild(newAnchor);
mentionList.appendChild(newListItem);
}
document.getElementById("mentions").setAttribute("class", ""); // unhide. hacky hack hack.
}
Unfortunately, clicking on one of these anchor tags results in a call like this:
loadUserNameInfo(undefined);
Why is this? My goal is an anchor like this:
<a onclick="loadUserNameInfo(someguy)">someguy</a>
How can I produce this?
Update This works:
newAnchor.onclick = function () { loadUsernameInfo(this.innerHTML) };
newAnchor.innerHTML = mentions[i];
The "i" reference inside the closure for the onclick handlers is trapping a live reference to "i". It gets updated for every loop, which affects all the closures created so far as well. When your while loop ends, "i" is just past the end of the mentions array, so mentions[i] == undefined for all of them.
Do this:
newAnchor.onclick = (function(idx) {
return function () { loadUsernameInfo(mentions[idx]) };
})(i);
to force the "i" to lock into a value idx inside the closure.
Your iterator i is stored as a reference, not as a value and so, as it is changed outside the closure, all the references to it are changing.
try this
function fillInMentioned(mentions) {
var mentionList = document.getElementById("mention-list");
mentionList.innerHTML = "";
for (var i = 0; i < mentions.length; i++) {
var newAnchor = document.createElement("a");
// Set the index as a property of the object
newAnchor.idx = i;
newAnchor.onclick = function () {
// Now use the property of the current object
loadUsernameInfo(mentions[this.idx])
};
// give this anchor the necessary content
newAnchor.innerHTML = mentions[i];
var newListItem = document.createElement("li");
newListItem.appendChild(newAnchor);
mentionList.appendChild(newListItem);
}
document.getElementById("mentions").setAttribute("class", "");
}

Categories