0. Credits to:
http://www.laruence.com/2009/09/23/1089.html
https://www.jeffjade.com/2015/08/03/2015-08-03-javascript-this/
https://www.youtube.com/watch?v=8aGhZQkoFbQ
https://www.zhihu.com/question/55050034
https://codepen.io/discountry/pen/XRXdVX?editors=1011
I. setTimeout
- After
delay
period, putcb
to taskQueue of main thread, rather than executingcb
right away!- only guarantees after exact
delay
,cb
is scheduled, - cannot guarantee
cb
being executed after exactdelay
, because maybe at that time, the main thread is still dealing a time-consuming task, which will postpone the execution ofcb
- only guarantees after exact
Good use case: recursively schedule delayed task, like a
node
data structure- deal with CPU intensive task, set aside period for UI update to be executed to avoid freezing
- eg. break down large task into smaller ones, schedule smaller task execution every delay period
- the bigger
delay
period is, the longer time left for UI - the time left for UI is exactly
delay
period - NOTE: don’t use
setInterval
, since the execution time forcb
can be longer thandelay
, resulting in there’s no time left for UI update. Whencb
finishes execution, the UI update and nextcb
will race to be executed in next event loop
- the bigger
setTimeout(cb, 0):
- 等到Call Stack为空,即下一轮 Event Loop的时候 执行
cb
- 等到Call Stack为空,即下一轮 Event Loop的时候 执行
- Render Queue 比 Callback Queue 在browser中的优先级高,他们共用一个JS V8 Engine。因此,callback中的
cb
会等到DOM被render之后在调用,这就是为什么使用setTimeout(cb, 0)
- Render Queue 比 Callback Queue 在browser中的优先级高,他们共用一个JS V8 Engine。因此,callback中的
- 这样,即便有很多
setTimeout(cb, 0)
短时间大量被调用,也会因为render queue的高优先级,使得它能够在两个cb
调用之间,插入到 JS V8 thread中update DOM,从而使页面不会freeze
- 这样,即便有很多
Waiting for elements mounted to DOM by GUI thread, then switch to JS thread, do the manipulation by
cb
Every function put inside
setTimeout
with0
delay, will respect all the DOM task, and will scheduled after all DOM tasckwill execute right after all synchronous task is finished on stack by main thread
i.e. has to wait for next event loop come
Great Use Case:
set the order of event handler:
1
2
3
4
5
6
7
8
9
10
11
12
13// 1. parent event_handler will be executed first,
// 2. then the target element
let input = document.getElementById('myButton');
input.onclick = function A() {
setTimeout(function B() {
input.value +=' input';
}, 0)
};
document.body.onclick = function C() {
input.value += ' body'
};
tackle the race problem of keypress
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27/*
keypress has two results:
1. give input the value of pressed key [asynchronous - hardware]
2. call the keypress event handler [asynchronous - event]
BAD version:
- cannot get the current key value when callback is called
- can only get the value of previous pressed key value
GOOD version:
- use setTimeout(fn, 0)
- callback will be executed after the key value is updated in DOM
*/
<input type="text" id="input-box">
// 1. BAD Version
document.getElementById('input-box').onkeypress = function (event) {
this.value = this.value.toUpperCase();
}
// 2. GOOD Version
document.getElementById('input-box').onkeypress = function() {
let self = this;
setTimeout(function() {
self.value = self.value.toUpperCase();
}, 0);
}
II. setInterval
- Every
delay
period, putcb
to taskQueue of main thread if there is nocb
waiting to be executed on the taskQueue; Otherwise, won’t schedulecb
evendelay
period is achieved Note:
- when delay is up, even there exists an CPU-intensive task ,
setInterval
won’t wait for the finish of the current task, instead put thecb
directly to taskQueue at the exact time spot; - But if the main thread is still executing task, the
cb
has to waits to be executed - There is NO accumulated
cb
scheduled on event queue, even if there is a CPU intensive task blocking the periodiccb
scheduling. setInterval
timer has a check mechnism to ensure, there will be only onecb
on event queue
- when delay is up, even there exists an CPU-intensive task ,
Common use case: CSS animation
Note1:
- it only focus scheduling
cb
afterdelay
, don’t care how long vacant time left - it’s possible that execution of
cb
is longer thandelay
, then nextcb
will be executed right after firstcb
finishes
- it only focus scheduling
Note2:
mustn’t use time variable as terminating condition to clear interval,
because some time it cost longer to execute
cb
, making some scheduled task won’t be executeduse other variable as terminating condition to clear interval
1
2
3
4
5
6
7
8
9
10let div = document.getElementById('someDiv');
let opacity = 1;
let fader = setInterval(function() {
opacity -= 0.1;
if (opacity >= 0) {
div.style.opacity = opacity;
} else {
clearInterval(fader); // use `opacity` as terminating condition
}
}, 100);
III. Common Problems
this
problem => because ofwindow.setTimeout
orwindow.setInterval
setTimeout( function(){ this.value=50 }, 1000 )
Problem:
- example above, exist
this
in the callback function, if notbind
actual object,this
will referwindow
setTimeout
setInterval
will change the reference ofthis
towindow
- example above, exist
Why?
this
always refers to the object who calls the function wherethis
resides- when
cb
executed, it is executed likecb()
, which omits the prefix object:window
How to solve?
use
bind()
to produce a newcb
function carrying this reference to right objectuse
apply
orcall
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18a = "string belongs to window" // note: not " let a = ... ";
obj = {
a: "string belongs to obj",
printA: function(){ console.log(this.a) }
}
// 1. "string belongs to window", after 1s
setTimeout(obj.printA, 1000)
// 2. "string belongs to window", after 1s
let temp = obj.printA
setTimeout(temp, 1000)
// 3. "string belongs to obj"
setTimeout(function(){ obj.printA.apply(obj) }, 2000)
// 4. "string belongs to obj"
setTimeout(function(){ obj.printA() }, 3000)
Implement a
setInterval
usingsetTimeout
NOTE:
- Approach 1: the
time_handle
should be an object if declared inside the function, rather than an number, because thetimeId
is updated everydelay
period Approach 2: the
time_handle
should be a global var outside the function , because thetimeId
is updated everydelay
period1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31// ----------------Approach 1----------------
function customSetInterval( cb, delay ){
let time_handle = {id: null}
function wrapper(){
cb.apply(null)
time_handle.id= setTimeout(wrapper, delay)
}
time_handle.id= setTimeout(wrapper, delay)
return time_handle
}
// Test
let i = 0
function cb(){ i+=1; console.log(i) }
let interval_handle = customSetInterval(cb, 1000) // print 1 , 2 , 3 , 4 , ...
// clearTimeout(interval_handle.id) // use to clear interval
// ----------------Approach 2----------------
let time_handle = null
function customSetInterval( cb, delay ){
function wrapper(){
cb.apply(null)
time_handle= setTimeout(wrapper, delay)
}
time_handle= setTimeout(wrapper, delay)
}
// Test
let i = 0
function cb(){ i+=1; console.log(i) }
customSetInterval(cb, 1000) // print 1 , 2 , 3 , 4 , ...
// clearTimeout(time_handle) // use to clear interval
- Approach 1: the
how to handle encoding task for a file (CPU intensive)?
Problem: it might freeze the UI when encode the whole file
Solution:
Divide the file into several part, using a symbol, or using size
for each small part, just take over CPU, since it’s small task
between each encoding, leave time for UI interaction
Note: use
setTimeout
instead ofsetInterval
Since the execution time for
cb
can be longer thandelay
, resulting in there’s no time left for UI update. Whencb
finishes execution, the UI update and nextcb
will race to be executed in next event loop1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26/*
input: the file to be encoded
output: the encoded goal file
totalSize: the size of the input file
encode: the function used to encode
*/
function encodeWrapper( input, output, totalSize, encode ){
let output = ""
let accumSize = 0
let i = 0
let timeoutHandle = null
/* delayed recursion
i: ith step
stepSize: how big to encod for a step
*/
function encodeHelper(i, stepSize ){
output += encode( input.substring(i*stepSize,stepSize) )
if( i*stepSize > totalSize ) {
clearTimeout(timeoutHandle)
return output
}
timeoutHandle = setTimeout(encodeHelper(i+1, stepSize), 100)
}
return encodeHelper(0, 1024)
}
How to write a delayed recursion ?
Problem: Calculate the sum from 0 to n
Solution: f(n) = f(n-1) + n; f(0) = 0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23/*
f(n) = f(n-1) + n
f(0) = 0
*/
function wrapper(n){
let timeHandle = 0
let accum = 0
// recursive call itself every 1000ms
function sum(n){
timeHandle=setTimeout(function(){
if (n === 0) {
clearTimeout(timeHandle)
alert(accum) // output the result
return
}
accum += n
sum(n-1)
},1000)
}
sum(n)
}
Debouncify a function
Problem: Some Ajax functions can be frequently called using events, which will cause severe performance problem on backend
Solution:
- we want to call Ajax function only after a period delay time (so that we can make sure the user is really waiting to for to make Ajax call, rather than in the middle of typing)
- when there is another intention to call the Ajax function in the middle of last delay, we want to reschedule the ajax call after another delay
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19function callAjaxRightAway(){
console.log("Ajax call starting, with arguments: " + [...arguments] )
}
function Debouncify( fn, delay ){
let time_handle = null
return function(){
clearTimeout(time_handle)
let args = arguments
time_handle = setTimeout( function(){ fn.apply(null,args) }, delay)
}
}
let delayedFun = Debouncify(callAjaxRightAway, 2000)
delayedFun({arr:[1, 2, 3], heigh: 10 }) // will be ignored
delayedFun(1,2) // will be ignored
delayedFun("a", "b", "c") // "Ajax call starting, with arguments: a,b,c"