This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 7 years ago.
Following this fiddle from this question, I wrote this bit of code:
var currentSlideCount = window.imgIds.length;
for (var i = 11; i < (currentSlideCount + 10); i++) {
// SET UP NEW SLIDE HTML
var newSlide = '<li><img id="apod' + i + '" class="rounded-corners apod-image"></li>';
$('#lightSlider').append(newSlide);
window.imgIds.push('apod'+i);
console.log(window.imgIds);
// GENERATE DATE
var date = new Date();
date.setDate(date.getDate() - i);
var day = date.getDate();
var month = date.getMonth();
var year = date.getFullYear();
console.log(year + "-" + month + "-" + day);
// GENERATE XML REQUEST
function foo(callback) {
var apodUrl = "https://api.nasa.gov/planetary/apod?concept_tags=True&date=" + year + "-" + month + "-" + day;
var apodXml = new XMLHttpRequest();
apodXml.open('GET', apodUrl, true);
apodXml.send(null);
// WHEN REQUEST IS READY
apodXml.onreadystatechange=function() {
if (apodXml.readyState==4 && apodXml.status==200) {
var apodParse = JSON.parse(apodXml.responseText);
callback(apodParse.url)
console.log(apodParse.url);
}
}
}
foo(function(result) {
var newSlideId = 'apod' + i;
document.getElementById(newSlideId).src = result;
});
Yet I'm still getting a Cannot set property 'src' of null console error on the img tag that was created long before its src attribute is being called on. And as far as I understand, I've set up the callback correctly. Why is this still not working?
First, you are declaring the function foo in a loop. While this is not leading to an error, it's bad practice. The function should be declared outside of the loop.
Second, the callback function passed into foo is invoked asynchronously (i.e., via AJAX). The variable i is assigned a value in the parent scope of the callback function and in a loop. The loop will have finished executing by the time the callback is invoked. When the callback is invoked, it will look up the scope chain to find the value of i and it will find i declared in the loop. i will be equal to the final value in the loop for which the loop conditional i < (currentSlideCount + 10) evaluates false and does not continue.
While this may be hard to follow, you can see what I mean by adding alert(i); to the callback function:
foo(function(result) {
alert(i);
var newSlideId = 'apod' + i;
document.getElementById(newSlideId).src = result;
});
You may be surprised to see that the alert will always display the same value for i.
To solve this problem, you need to use an immediately executing function to create a new scope where i is passed by value as the proper value you want.
Change this:
foo(function(result) {
var newSlideId = 'apod' + i;
document.getElementById(newSlideId).src = result;
});
To this:
foo(
(function(i) {
return function(result) {
var newSlideId = 'apod' + i;
document.getElementById(newSlideId).src = result;
}
})(i)
);
In JavaScript, scope is delineated at the function level. By using an immediately executing function, you are adding a new scope where i has been passed by value for the current iteration of the loop.
Variable scoping in JavaScript can be tricky to understand and your question hits right at one of the more complex scenarios. You may find it useful to review some other explanations of scoping in JavaScript.
Two problems there:
You're using a function declaration inside a control structure. You can't do that, it's invalid; some browsers will try to rewrite it for you as a function expression, but others will not. Function declarations are only valid at the top level of a scope, outside of all control structures. E.g., at global scope or at the top level of a function.
More importantly, the callback you're passing to foo has an enduring reference to the i variable, not a copy of it as of when the function was created. So they all see i as it is when they run, later, at the end of the loop.
Move your foo function out of the control structure, and ideally parameterize it, in particular passing it the i value that the callback should use. E.g.:
var currentSlideCount = window.imgIds.length;
for (var i = 11; i < (currentSlideCount + 10); i++) {
// SET UP NEW SLIDE HTML
var newSlide = '<li><img id="apod' + i + '" class="rounded-corners apod-image"></li>';
$('#lightSlider').append(newSlide);
window.imgIds.push('apod' + i);
console.log(window.imgIds);
// GENERATE DATE
var date = new Date();
date.setDate(date.getDate() - i);
var day = date.getDate();
var month = date.getMonth();
var year = date.getFullYear();
console.log(year + "-" + month + "-" + day);
foo(year, month, day, i, function(result, index) {
var newSlideId = 'apod' + index;
document.getElementById(newSlideId).src = result;
});
}
// GENERATE XML REQUEST
function foo(year, month, day, index, callback) {
var apodUrl = "https://api.nasa.gov/planetary/apod?concept_tags=True&date=" + year + "-" + month + "-" + day;
var apodXml = new XMLHttpRequest();
apodXml.open('GET', apodUrl, true);
apodXml.send(null);
// WHEN REQUEST IS READY
apodXml.onreadystatechange = function() {
if (apodXml.readyState == 4 && apodXml.status == 200) {
var apodParse = JSON.parse(apodXml.responseText);
callback(apodParse.url, index)
console.log(apodParse.url, index);
}
}
}
Related
This question already has answers here:
What does var mean in Javascript? [closed]
(2 answers)
Closed 2 years ago.
<script>
var start;
function shapeAppear() {
document.getElementById("shapes").style.display = "block";
var start = new Date().getTime();
}
function delay() {
setTimeout(shapeAppear,Math.random() *2000);
}
delay();
document.getElementById("shapes").onclick = function() {
document.getElementById("shapes").style.display = "none";
var position = Math.floor(Math.random() * 500);
document.getElementById("shapes").style.left = position + "px";
document.getElementById("shapes").style.right = position + "px";
document.getElementById("shapes").style.top = position + "px";
document.getElementById("shapes").style.bottom = position + "px";
var end = new Date().getTime();
var time = end - start;
time /= 1000;
document.getElementById("time").innerHTML = time + "s";
delay();
}
</script>
Here in this code i want the date() function to return a specific integer value.
Because when we subtract the two Date() functions we must get the integer value.
It is a scoping issue. if you use var inside a function, that variable will only exist in the scope of that function.
So what you could do is this:
var start;
function shapeAppear() {
start = new Date().getTime();
}
By removing var in the shapeAppear function, you're updating the var that is created outside the function.
Besides that as Rodney mentioned you call delay before shapeAppear which means that start is not defined when calling delay.
Hope that makes sense.
I have a time function
function hm()
{
var d = new Date();
var h = ('0'+d.getHours()).substr(-2);
var m = ('0'+d.getMinutes()).substr(-2);
var str0 = h + ':' + m;
return {date: str0, hour: h, minute: m};
}
var hm = hm();
var date = hm.date;
var hour = hm.hour;
var minute = hm.minute;
and I have a div
<div id="mytime">Time</div>
I call time function from this which updates the div
function refreshDiv() {
document.getElementById('mytimer').innerHTML = 'Time:.....' + hm.date;
}
$(document).ready(function () {
setInterval(refreshDiv, 5000);
});
It won't update the time like 12:03 12:04 but if I place function hm() and variables var hm = hm(); var date = hm.date; into function refreshDiv() block it works.
How can I make it work?
EDIT:
This is a nwjs project.
If I add hm = hm(); inside refreshDiv() I get:
"Uncaught TypeError: object is not a function", source: file:///home/.../index.html (182)
182 is line number of included hm = hm();
You don't need to store individual values:
var hm = hm();
var date = hm.date;
var hour = hm.hour;
var minute = hm.minute;
You have a function hm(): call it, and immediately get the value of a returned object:
document.getElementById('mytimer').innerHTML = 'Time: ' + hm().date;
Why? Let's use Chrome DevTools to clarify this.
So, you defined a function hm().
When you call it, you get the returned object: {a: 0000000000000, b: "test"}
You can access the fields of this object with .
var object = hm(); // Assign the returned value of hm() to object
alert(object.a); // Alerts value of the field a of object
If you don't want to allocate new variables, use hm().a. You will conserve memory and time.
Say I have a function OfInterest, called by functions A and B, and that calls function X and Y. I'm looking for a way of viewing:
OfInterest -- 200 ms total time
X -- 150 ms total time
Y -- 50 ms total time
...such that it includes both the calls to OfInterest by A and B.
In the Chrome profiler, this would be the top-down view zoomed in on OfInterest, except that AFAIK there's no way of including calls to OfInterest from both A + B at the same time. The bottom-up view gets the right total time for OfInterest, but AFAIK there's no way of seeing X + Y in that view.
Is there a way of getting Chrome to spit this out, or using a different profiler such as Firebug to see this?
This github project gives top down tree for jvascript call stat
https://github.com/brucespang/jsprof
When i was looking for a javascript function call profiler i found a small script, which i modified as per my need, this script is very simple it will show statistics of all global functions in that window object though it doesnt list names of nested functions called.
function callLog()
{
var functionPool = {}
for( var func in window )
{
if (typeof(window[func]) === 'function')
{
functionPool[func] = window[func];
(function(){
var functionName = func;
var totalTime= 0;
var noOfTimes =0;
var minTime= 0;
var maxTime =0;
window[functionName] = function(){
var args = [].splice.call(arguments,0);
var startTime = +new Date;
functionPool[functionName].apply(window, args );
var duration = new Date - startTime;
if (duration> maxTime)
maxTime= duration;
if (duration< minTime)
minTime= duration;
totalTime = totalTime + duration;
noOfTimes++ ;
console.log('Executed: ' + functionName + '('+args.join(',')+')' + " Time[" + duration + "ms]"+" Total Time[" + totalTime +"ms]" +" No of Times[" + noOfTimes + "]" + "max Time [" + maxTime+ "ms]" + "min Time [" +minTime +"ms]");
}
})();
}
}
}
Suppose we define a function that simply increments its input by some stored value dd:
var obj={}
obj.dd=1
obj.f=function(x){
return x+this.dd
}
Alternatively you could create a closure for dd as follows but this would create a static increment as opposed to one that could be altered later:
var dd=1
var f=function(x){
return x+dd
}
We could alternatively store dd in the function itself:
var obj={}
obj.f=function(x){
return x+this.f.dd
}
obj.f.dd=1
I am curious as to whether it is possible for a function to retrieve a variable attached to itself without going through a parent object, something like a self keyword that would refer to the function itself and would allow the following:
var f=function(x){
return x+self.dd
}
f.dd=1
I know it is unnecessary to do such a thing but I think it would be cool if you could.
You can give function literals a name:
var f = function me(x) {
return x + me.dd;
};
f.dd = 1;
This doesn’t work properly in older versions of IE/JScript, though, as me and f don’t reference the same object. The (deprecated and not usable in strict mode) alternative is arguments.callee:
var f = function(x) {
return x + arguments.callee.dd;
};
f.dd = 1;
Also, your note about the closure isn’t quite right; it can be altered later, even through another function:
var dd = 1;
var f = function(x) {
return x + dd;
};
var setdd = function(_dd) {
dd = _dd;
};
A function is an object. If you reference the var holding the function:
var f = function (x) {
return x + f.dd
};
f.dd = 1;
alert(f(1));
result: 2
If the function is named, you can do the same:
function foo(x) {
return x + foo.dd;
}
foo.dd = 1;
alert(foo(1));
result: 2
I have this data response from an AJAX call:
{"18:00":{"twopersons":1,"fourpersons":0}}
Which gets stored into a variable by statsarray = data;
Now how can i loop through statsarray and output the twopersons value?
So I can alert:
18:00 - There's 2 x 2persons and 0 x 4persons
Here is the Ajax call:
var statsarray;
var currentloopeddate = test_date.toString('yyyy-MM-dd')
$.post("/home/sessions",
{ action: 'partner_calendar_checkseats', date: currentloopeddate },
function(data) { statsarray = data; }
);
Just do the following:
var twopersons = data["18:00"].twopersons;
var fourpersons = data["18:00"]["fourpersons"];
(Both variants are possible)
A variant would be:
var shorter = data["18:00"];
var twopersons = data.twopersons;
// ...
Something like:
var tst = {"18:00":{"twopersons":1,"fourpersons":0}};
for(k in tst) {
for(var z in tst[k]) {
console.log(k + ": Theres "+tst[k][z] + " X " + z);
}
}
You can try something like this:
(UPDATE: better example)
var statsarray = {"18:00":{"twopersons":1,"fourpersons":0}};
var hour, persons, line, array;
for (hour in statsarray) {
if (statsarray.hasOwnProperty(hour)) {
array = [];
for (persons in statsarray[hour]) {
if (statsarray[hour].hasOwnProperty(persons)) {
array.push(statsarray[hour][persons] + " x " + persons);
}
}
line = hour + " - There's " + array.join(' and ');
alert(line);
}
}
See: DEMO.
Unfortunately you have to test with .hasOwnProperty to make sure it will work with some libraries.
UPDATE: You have added the code from your AJAX call in your question and I noticed that you declare the statsarray variable outside the callback function, but assign some value to that variable inside the callback. Just keep in mind that you have to run your iteration code inside the function that is the AJAX callback, where you have: statsarray = data; - just after this line, to make sure that you actually have some values to iterate over.