4 ms·
So, I've worked out the Javascript problem several times in my head. Could it be because num is in a difference context when it is being called by setTimeout()?
by chacha102 16y ago
So, I've worked out the Javascript problem several times in my head. Could it be because num is in a difference context when it is being called by setTimeout()?
I just can't figure it out, and it is bugging me. (Guess I won't get the job...)
- spez 16y agofunction countdown (num) { for (var i = 0; i <= num; i += 1) { var make_cb = function (n) { return function () {alert(num - n)}; } setTimeout(make_cb(i), i * 1000); } }
- ivankirigin 16y agoDon't answer challenge questions, bro
- spez 16y agoIf Dropbox doesn't want their questions answered, they shouldn't harangue people for up-votes.
- ivankirigin 16y agolol I guess so
- deleted 16y ago[deleted]
- toni 16y agoA slightly different version: function countdown (num) { for (var i = 0; i <= num; i += 1) { setTimeout(function (i) { return function(){ alert(num - i); } }(i), i * 1000); } } countdown(5);
- vinhboy 16y agocheater way: alert(num--); LoL.
- 9oliYQjP 16y agoJust curious about your wording. Why do you say this is the cheater way? It might stop working for large values of num where the anonymous function scheduled by the setTimeout begins executing before the for loop finishes. But it's certainly valid for a particular range of values. EDIT: On second thought, does anyone even know if the scenario I mentioned above is plausible? I changed the alerts to console.logs and I'm finding it pretty damn impossible to not get the expected results using the "cheater way" even for large values of num.
- bitanarch 16y agoThat depends on how the setTimeout() actually schedules things in the JavaScript engine. Recall that JavaScript is (before Chrome and worker threads appeared) traditionally single threaded - normally, even when you say setTimeout(fn, 0) - it's just putting your fn()'s execution at the end of the run loop - or whatever the scheduler calls its list of things to do. So, even though your current execution slice in the run loop may take like 1 minute, your fn() will still not execute until you're done what you're currently doing.
- chc 16y agoIt's cheating because the point of the "challenge" is to see if you understand how scope works in Javascript — particularly with loops and closures, which confuse a lot of people — and how to use it properly. This "cheating" solution simply sidesteps the whole question by having the closures mutate a variable that the rest of the function doesn't touch. And no, I don't think the potential problem you see is much of a problem at all. The function will keep executing even if the timers fire. As long as the timeouts execute in order and the countdown function is able to queue them in less time than they take to execute, it will work.
- lg 16y ago1.7: function countdown (num) { for (var i = 0; i <= num; i += 1) { let (j = num - i){ setTimeout(function () { alert(j); }, i * 1000); } } }
- sgk284 16y agoI understand the scoping issue here, but the recursive solution is just all around better: function countdown(num) { if(num < 0) { return; } setTimeout(function() { alert(num); countdown(num - 1); }, 1000); } Note: I'm aware that the dropbox version shows the first alert without any delay, whereas this waits a second before showing anything. This is slightly different behavior, but arguably acceptable.
- abstractbill 16y agoYou can make the recursive solution work without any delay: function countdown(n) { if(n >= 0) { alert(n); setTimeout(function() {countdown(n-1);}, 1000); } }
- deleted 16y ago[deleted]
- chacha102 16y agoThat is what I suspected. Just wanted to confirm that.
- deleted 16y ago[deleted]
- weixiyen 16y agoBefore anyone answers, probably best to let people figure it out on their own.
- andreyf 16y agoThe answer is explained in depth in Crockford's JavaScript: The Good Parts (Chapter 4, if I remember right).
- ydant 16y agoWell, num is the stable variable, it's i that's the issue. Grokking what's going on here is one of the more important parts of using closures without introducing unexpected bugs.
- ww520 16y agoIt has to do with Javascript's broken closure support that causes this kind of surprises in coding. Traditional closure closes over the current variable values of a frame environment at each creation of an inner function instance. A new frame environment is cloned for the function instance with all the current variable values sealed. That's why it's called a closure; it's closed, sealed, that no one has access to it except the particular function instance. Javascript cheats in its closure implementation. It doesn't create a new frame environment for each inner function instance, instead just references back to the parent's frame environment. Thus all inner function instances share the same frame environment. Any code in the parent function or in any function instance modifying a variable will instantly affect all the function instances. Closure has lost its meaning in Javascript. It should be called Shareture.
- axod 16y agoIt's not broken, it's just the way it works in js. Maybe it should be called something else, but then maybe people should read the language specs better to see how things work in js...
- ww520 16y agoDocumenting it is how bug becomes feature. :)
- petdog 16y agoIt's not broken at all, and not in any way peculiar to javascript. >>> map(apply, [lambda:i for i in range(5)]) [4, 4, 4, 4, 4] * (mapcar 'funcall (loop for i from 0 to 5 collect (lambda () i))) (6 6 6 6 6 6) It's just that the looping constructs don't introduce a new binding, but modify the existing one. >>> map(apply, [lambda j=i:j for i in range(5)]) [0, 1, 2, 3, 4] * (mapcar 'funcall (loop for i from 0 to 5 collect (let ((j i)) (lambda () j)))) (0 1 2 3 4 5)