js 究竟是如何運作?
js is a single-threaded non-blocking asynchronous concurrent languange. js has a call stack, an event loop, a call back queue, some other apis and stuff.
v8, which is js runtime inside chrome has a call stack and heap
The heap, where memory allocation happens, and then there's the call stack, which is where your stack frames are and all that kind of stuff, but, if you, like, clone the V8 code base and grep for things like setTimeout or DOM or HTTP request, they're not in there, they don't exist in V8.
we have the V8 Runtime but then we have these things called web APIs which are extra things that the browser provides. DOM, AJAX, time out, things like that, we have this mythical event loop and the callback queue
the call stack
one thread == one call stack == one thing at a time
JavaScript is a single threaded programming language, single threaded Runtime, it has a single call stack. And JS can do one thing at a time, that's what a single thread means, the program can run one piece of code at a time.
ex1
function multiply(a, b) {
return a * b
}
function square(n) {
return multiply(n, n)
}
function printSquare(n) {
var squared = square(n)
console.log(squared)
}
printSquare(4)
the call stack is basically ‑‑ it's a data structure which records basically where in the program we are, if we step into a function, we put something on to the stack, if we return from a function, we pop off the top of the stack that's all the stack can do, ‑‑ so if you run this file, there's kind of a main function, right, like the file itself, so, we push that on to the stack
執行流程
- 執行檔案 => put main( ) on the stack
- printSquare(4) is a function call => put printSquare(4) on the stack
- 進入 printSquare 裡面,square(n) is a function call => put square(n) on the stack
- 進入 square 裡面,multiply(n, n) is a function call => put multiply(n, n) on the stack
- 進入 multiply 裡面,return a * b => pop out sth off the stack => 將 multiply(n, n) 移除
- return multiply(n, n) => pop out sth off the stack => pop multiplier of the stack return to square => 將 square(n) 移除
- 回到 printSquare => console.log(squared) => put console.log(squared) on the stack
- 雖然 printSquare 之後沒有 return,但我們要結束 function => 所以最後將這些 stack pop out
ex2-blowing the stack
We have a function main which calls foo which calls foo, which calls foo, which calls foo, and ultimately chrome says, you probably didn't mean to call foo 16,000 times recursively, I'll just kill things for you and you can figure out where your bug lies, right.
block
So, the big question then comes is like what happens when things are slow?
there's no strict definition of what is and didn't blocking, really it's just code that's slow.
So console.log isn't slow, doing a while loop from one to ten billion is slow, network requests are slow. Image requests are slow. Things which are slow and on that stack are what are blocking means.
ex1
var foo = $.getSync('//foo.com')
var bar = $.getSync('//bar.com')
var qux = $.getSync('//qux.com')
console.log(goo)
console.log(bar)
console.log(qux)
以此類推
we're doing network request, network is relative to computers, are slow, hopefully that network requests completes, we can move on, wait, move on.
Wait, and, I mean, this network request might never finish, so ... yeah, I guess I'll go home. Finally those three, you know blocking behaviors complete and we can clear the stack, right.
in a programming language is single threaded, that's what happens, right, we make a network request, we have to just wait till it's done, because we have no way of handling that.
why is this a problem ? because of browsers
We've got the synchronous request, the browser can't do anything else. It can't render, it can't run any other code, it's stuck. Not ideal, right if we want people to have nice fluid UIs, we can't block the stack.
asynchronous callbacks
there's almost no blocking functions in the browser, equally in node, they're all made asynchronous, which basically means we run some code, give it a callback, and run that late
console.log('hi')
setTimeout(function() {
console.log('there')
}, 5000)
console.log('JSConfEU')
stack 執行流程
- main( ) 放入
- console.log('hi') pop in
- console.log('hi') pop out
- setTimeout. We know it doesn't run immediately, we know it's going to run in five seconds time, we can't push it on to the stack, somehow it just disappears
- console.log('JSConfEU') pop in
- console.log('JSConfEU') pop out
- wait for 5 secods => console.log('there') pop in
- console.log('there') pop out
We log JSConfEU, clear, five seconds later somehow magically "there" appears on the stack. How does that happen? And that's ‑‑ this is basically where the event loop comes in on concurrency.
Concurrency & the Event loop
One thing at a time except not really
JavaScript Runtime can only do one thing at one time. It can't make an AJAX request while you're doing other code. It can't do a setTimeout while you're doing another code. The reason we can do things concurrently is that the browser is more than just the Runtime.
So, remember this diagram, the JavaScript Runtime can do one thing at a time, but the browser gives us these other things, gives us these web APIs, these are effectively threads, you can just make calls to, and those pieces of the browser are aware of this concurrency kicks in. If you're back end person this diagram looks basically identical for node, instead of web APIs we have C++ APIs and the threading is being hidden from you by C++
執行流程:
- run file => pop in main( ) at stack
console.log('hi')
=> pop in log('hi') at stack => console 呈現 hi- pop out log('hi') at stack
- call setTimeout => we pass this callback function and a delay to the setTimeout call => pop in (setTimout cb) at stack
- setTimeout is an API provided to us by the browser, it doesn't live in the V8 source => (timer cb) at web api => pop off (setTimout cb) at stack
console.log('JSConfEU')
=> pop in log('JSConfEU') at stack => console 呈現 JSConfEU- we've got this timer in the web API, which five seconds later is going to complete => the web API can't just start modifying your code, it can't chuck stuff onto the stack when it's ready if it did it would appear randomly in the middle of your code so this is where the task queue or callback queue kicks in.
- (執行原理:web APIs 執行完後,回傳給 task queue,剩下的交給 event loop 去處理) Any of the web APIs pushes the callback on to the task queue when it's done => The event loop's job is to look at the stack and look at the task queue. If the stack is empty it takes the first thing on the queue and pushes it on to the stack which effectively run it.
- the stack is clear, there's a callback on the task queue, the event loop runs, it says, oh, I get to do something, pushes the callback on to the stack. Remember it's the stack is like JavaScript land, back inside V8, the callback appears on the stack, run, console.log “there”, and we're done.
console.log('hi')
setTimeout(function() {
console.log('there')
}, 0)
console.log('JSConfEU')
setTimeout 0 -- The reason is, generally, if you're trying to defer something until the stack is clear
- 結果和上面一樣
- 流程:The setTimeout zero, now it's going to complete immediately and push it on to the queue => since the property of event loop, it has to wait till the stack is clear before it can push the callback on to the stack, so your stack is going to continue to run, console.log “hi”, “JSConfEU” and clear, now the event loop can kick in and call your callback.
- 小結:That's like an example of setTimeout zero, is deferring that execution of code, for whatever reason to the end of the stack. Or until stack is clear.
同理,for Ajax
console log, “hi”, make an AJAX request, the code for running that AJAX request does not live in JavaScript Runtime but in the browser as a web API, so we spin it up with a callback in the URL, your code can continue to run. Until that XHR request completes, or it may never complete, it's okay, the stack can continue to run, assuming it completes, gets pushed to the queue,picked up by the event loop and it's run.
By the time the callbacks get queued... that fourth callback we asked for a one second delay, and it's still waiting, the callback hasn't run, right. This illustrates the ‑‑ like what time out is actually doing, it's not a guaranteed time to execution, it's a minimum time to execution, just like setTimeout zero doesn't run the code immediately it runs the code next‑ish, sometime, right?
call back
depending on who, speak to and how they phrase things, callbacks can be one of two things
- callbacks can be any function that another function calls
- callbacks can be more explicitly an asynchronous callback as in one that will get pushed back on the callback queue in the future
// 狀況一
[1, 2, 3, 4].forEach(function(i) {
console.log(i)
})
// 狀況二
function.asyncForEach(arr, cb) {
arr.forEach(function() {
setTimeout(cb, 0)
})
}
asyncForEach([1, 2, 3, 4], function(i) {
console.log(i)
})
狀況一:The forEach method on an array, it doesn't run, it takes a function, which you could call a callback, but it's not running it asynchronously, it's running it within the current stack. for the first block of code that runs, it's going to sit and block the stack until it's complete.
狀況二:whereas in the Async version, okay, it's slowed down, but we're basically going to queue a bunch of callbacks and they're going to clear and then we can actually run through and do a console.log
render
it can't actually do a render if there is code on the stack, right. Like the render kind of call is almost like a callback in itself. It has to wait till the stack is clear.
The difference is that the render is given a higher priority than your callback, every 16 milliseconds it's going to queue a rend, wait till the stack is clear before it can actually do that render
So this is ‑‑ this render queue is just simulating a render, every second it's can I do a render? Yes, can I do a render? Yes.
狀況一、非同步
we're doing this slow synchronous loop through the array, our render is blocked, right, if our render is blocked you can't select text on the screen, you can't click things and see the response, right, like the example I showed earlier
狀況二、同步
it's blocked while we queue up the async time out, that relatively quick but we're given ‑‑ we're kind of giving the render a chance between each element because we've queued it up asynchronously to jump in there and do the render.
when people say don't block the event loop, this is exactly what they're talking about. They're saying don't put shitty slow code on the stack because when you do that the browser can't do what it needs to do, create a nice fluid UI.