js在誕生初期就肯定了其單線程的定位,也就是說,全部任務須要排隊,前一個行行執行,前面的執行完,纔會執行後面的。若是前一個任務耗時很長,後一個任務就不得不一直等着。
在討論單線程的js時,咱們先來看看爲何js要是單線程的。讓js變成多線程的不行麼🙄~~
css
有個叫 Web Worker 的東西,可讓瀏覽器開出一個Worker 線程,經過接受發送消息和主線程交互,而且和主線程不衝突的執行。web worker的定位是負責須要大量計算的代碼執行線程。它限制了咱們不能訪問 DOM 對象。
html
var a = 0
while (a < 10000000) {
a++
}
console.log(10000000)
console.log(123)
// 123
// 321
複製代碼
看上面這個代碼,先執行while 循環,在打印 a,最後123,這很好理解,js就是單線程,一行一行解釋執行。就算while 要加一個億,後面的代碼也得等着java
那咱們的代碼是否是就沒辦法異步執行了呢?固然不是。咱們的代碼須要承擔一個很是重要的任務就是,完成和用戶的交互。好比你點擊一個按鈕但願它能給你彈出一個彈窗。web
// 來看個很簡單的例子
const body = document.querySelector('body')
console.log(1)
body.addEventListener('click',function(){
console.log(body)
})
body.addEventListener('click',function(){
console.log(body)
})
console.log(2)
console.log(2)
console.log(2)
// 下面能夠有無限代碼
...
複製代碼
這段代碼。在咱們不點擊頁面的時候,是不會打印body的。這就是事件監聽經過事件監聽咱們能夠實現,一個異步任務。
ajax
寫着寫着感受仍是須要簡單的說一個ur從輸入框到加載完成的主要流程。瀏覽器
這裏每一個展開均可以寫N+1盤文章。咱們重點了解一下最後一個,瀏覽器解析html的時候會從上到下。在沒有碰到js代碼以前,瀏覽器會一邊解析css一邊解析dom,最後把他們合併渲染。若是中途遇到了js,瀏覽器會中斷解析dom,去解析js,而後從新解析dom,渲染。
服務器
咱們再重點瞭解一下瀏覽器對js的解析
網絡
遇到script,開啓一個宏任務吧。解析裏面的js,預解析var和function聲明的東西。值得注意的是JavaScript預解析不僅是在一開始。每一個執行上下文都會進行一次預解析。
多線程
function fun () {
console.log(a)
var a = 1
}
fun() // undefined
複製代碼
爲啥會是undefined,而不是由於找不到a報錯呢?緣由就是js預解析了一次,實際執行的代碼咱們能夠理解爲是這樣的dom
function fun () {
var a = undefined
console.log(a)
a = 1
}
fun() // undefined
複製代碼
這個預解析也是咱們能夠在函數定義前執行function的緣由。
fun() // 1
function fun () {
var a = 1
console.log(a) // 1
}
複製代碼
思考一下一個需求。讓一些前面的代碼在最後執行。這時小明說,很簡單。把它放在最後不就好了嗎😋。小明你出去🤣...
console.log('翻天')
var a = 100000
for(let i in [123, 123, 123, 123]) {
}
while (a > 0) {
a--
}
alert(a)
alert(a)
var ajax=new XMLHttpRequest();
ajax.onreadystatechange=function(){
console.log('ajax', ajax.responseText);
}
ajax.open("GET","https://search-merger-ms.juejin.im/v1/search?query=ajax&page=0&raw_result=false&src=web",true);
ajax.send();
// 翻天
// alert(0)
// alert(0)
// ajax, '******'
...
複製代碼
能夠看到最早打印翻天,而後在一行一行執行下去了,最後打印 這個異步ajax返回的結果。
那怎麼辦呢。咱們找到了小明,小明把cosnole.log('翻天')放到了代碼的最後面。執行一下。發現竟然仍是比ajax先執行...
// 這樣就行啦
setTimeout(() => {
console.log('翻天')
}, 0)
...
...
...
// alert(0)
// alert(0)
// ajax, '****'
// 翻天
複製代碼
你們能夠在控制檯執行一下。
瀏覽器的線程
js引擎線程 (解釋執行js代碼、用戶輸入、網絡請求)
GUI線程 (繪製用戶界面,就是咱們剛剛說的解析css和dom的,它和js主線程是互斥的)
http網絡請求線程 (處理ajax的)
定時觸發器線程 (setTimeout、setInterval)
瀏覽器事件處理線程 (將click、touch放入事件隊列中)
那爲何setTimeout 比 ajax還後執行呢?
由於瀏覽器從開始執行遇到script會開啓一個宏任務。遇到setTimeout也會開啓一個宏任務。遇到ajax請求開啓的是一個微任務。只有當一個宏任務全部的同步代碼和因此微任務所有執行完畢後,瀏覽器纔會開始下一個宏任務。這裏的setTimeout 屬於下一個宏任務。
難度升級,咱們再思考下面這些代碼
<script>
console.log(1)
setTimeout(() => {
console.log(2)
}, 0)
const prom = new Promise(function (ret, rej) {
console.log(3)
const ajax = new XMLHttpRequest();
ajax.onreadystatechange=function(){
ret(4)
}
ajax.open("GET","https://search-merger-ms.juejin.im/v1/search?query=ajax&page=0&raw_result=false&src=web",true);
ajax.send();
console.log(5)
setTimeout(() => {
console.log(6)
}, 0)
})
prom.then(res => {
console.log(res)
setTimeout(() => {
console.log(7)
})
})
setTimeout(() => {
console.log(8)
})
console.log(9)
<script/>
複製代碼
看到這段代碼有沒有感受日了狗🐩。思考一下,會打印啥。
// 1 3 5 9 4 2 6 8 7
複製代碼
那麼你的答案是對的嗎?若是是,恭喜你很牛逼😀...
咱們從1到8開始講講爲何會是這個順序,而不是123456789.依次打印呢?
由於js引擎遇到 setTimeout 會開啓一個宏任務,new Promise().then() 是一個微任務,這裏的Promise是屬於 script 這個宏任務的。 執行順序是先進先出。
經過js事件隊列實現這樣一個需求
obj.eat('a') // 立刻打印'a'
obj.stop(3000).eat('a') // 間隔3秒後打印 'a'
複製代碼
思考一下。
讓咱們來看看一個簡單的實現代碼吧
class laz {
constructor(name) {
this.tasks = []
setTimeout(() => {
this.next()
}, 0)
}
next () {
let task = this.tasks.shift()
task && task()
}
eat (val) {
const task = (val => () => {
console.log(val)
this.next()
})(val)
this.tasks.push(task)
return this
}
stop (time) {
const task = (time => () => {
setTimeout(() => {
console.log(time)
this.next()
}, time)
})(time)
this.tasks.push(task)
return this
}
}
const obj = new laz()
obj.eat('a') // a
obj.stop(3000).eat('a') // 三秒後 3000 a
複製代碼
這個功能就是利用了js隊列實現了一個簡單的延遲執行.
其實這樣的例子還有不少.
總結:其實一開始只是想寫幾百個字結束戰鬥...結果一寫發現,就如今仍是沒寫全。每一個知識點都牽扯到一大堆相關聯的知識點。每一個展開說均可以說半天!!
我想說的是什麼呢,就是平時咱們在學習中,不要死記硬背,東西是背不完的,咱們的時間和大腦都是有限的,記住索引便可。
說實話這麼一大片長文字,我本身都不想看
最後送上一句話
吾生也有涯,而知也無涯