學(xué)習(xí)總覽
JavaScript
- 運行機制
- 渲染引擎
- JavaScript引擎
- 調(diào)用棧
- 事件循環(huán)
CSS
- 盒模型的理解
HTML
- HTML語義化
學(xué)習(xí)內(nèi)容
(1)JavaScript運行機制
談?wù)劸€程問題
JavaScript是一門單線程運行的語言。那么為什么JavaScript是單線程呢?我們可以假設(shè)JavaScript是多線程的,線程1和線程2并行,線程1刪除了DOM節(jié)點A,線程2卻要修改DOM節(jié)點A,那么當(dāng)線程2操作該節(jié)點的時候我們到底該怎么處理呢?這種不確定性導(dǎo)致我們必須保證DOM的狀態(tài)是唯一的,所以JavaScript變成了一門單線程語言。
同步異步
既然都說了JavaScript是單線程的,那么必然就會存在執(zhí)行內(nèi)容擁堵的情況,例如最常見的網(wǎng)絡(luò)請求,當(dāng)請求發(fā)給后端的時候,我們是不會等到數(shù)據(jù)回來再執(zhí)行接下來的代碼的,所以任務(wù)執(zhí)行就分成了兩種:
- 同步任務(wù)
- 異步任務(wù)
當(dāng)主線程遇到同步任務(wù)時會直接執(zhí)行,并且這些同步任務(wù)的執(zhí)行會形成一個執(zhí)行棧,而當(dāng)主線程碰到異步任務(wù)的時候,異步任務(wù)會暫時性地被跳過,讓主線程接著去執(zhí)行接下來其他的同步任務(wù),當(dāng)那些暫時被跳過的異步任務(wù)有了結(jié)果,就會立即被推進一個任務(wù)隊列(task queue),等到主線程空出來后,主線程會主動去檢查這個任務(wù)隊列,看里面都有什么內(nèi)容等待執(zhí)行,然后依次執(zhí)行即可,這里有一個很關(guān)鍵的點,如果我們寫了兩個平行關(guān)系的ajax請求,那么這兩個異步請求的回調(diào)誰先執(zhí)行是不確定的,原因就是異步請求誰先進入任務(wù)隊列的順序在沒有經(jīng)過限制(例如promise的then回調(diào)限制)的情況下是不確定的。
說了這么多,我們還是放個圖來直觀感受一下吧:

宏任務(wù)/微任務(wù)
在了解宏任務(wù)和微任務(wù)的特點之前,我們只需要記住一句話,在當(dāng)前微任務(wù)沒有完成之前,是不會執(zhí)行下一個宏任務(wù)的。
在微任務(wù)、宏任務(wù)與Event-Loop一文中,作者借助銀行辦理業(yè)務(wù)的隊伍形象地解釋了宏任務(wù)和微任務(wù)的差別,我提煉了作者傳達出的兩個要點:
- 微任務(wù)的任務(wù)隊列不為空時,主線程是不會去執(zhí)行宏任務(wù)隊列中的宏任務(wù)的
- 在不同的運行環(huán)境下宏/微任務(wù)的種類有細微差別
在瀏覽器以及node環(huán)境下,宏任務(wù)和微任務(wù)列表如下:


我們結(jié)合上面兩個表格來看一段代碼:

我大致繪制了一個流程如下:

回調(diào)隊列
回調(diào)隊列也稱為事件隊列或者消息隊列,本身就是用于幫助Web API模塊處理異步操作的。在Web API模塊中,異步操作在相應(yīng)的線程中處理完成得到結(jié)果之后,會把結(jié)果注入異步回調(diào)函數(shù)的參數(shù)中,并且把回調(diào)函數(shù)推入回調(diào)隊列中。
雖然我們將異步操作的結(jié)果推入了回調(diào)隊列中,但什么時候?qū)⑦@些結(jié)果拿給主線程調(diào)用棧去執(zhí)行呢?這就要說到事件循環(huán)了。
回調(diào)隊列
隊列是一個FIFO,先進先出的存儲結(jié)構(gòu),
這樣意味著異步操作的回調(diào)函數(shù)會按照進入隊列的順序被執(zhí)行,而不是調(diào)用的順序被執(zhí)行。
事件循環(huán)(Event Loop)
事件循環(huán)所做的事情就是不斷不斷在主線程調(diào)用棧與回調(diào)隊列中之間來回檢查,首先檢查主線程調(diào)用棧是否為空了,如果為空就去回調(diào)隊列中拿取第一個任務(wù)放入調(diào)用棧,依次循環(huán),直到調(diào)用棧以及回調(diào)隊列都為空。
借助一道面試題小試牛刀
在搜集資料的過程中,我發(fā)現(xiàn)了一道頻繁出現(xiàn)的頭條面試題,在體現(xiàn)JavaScript運行機制與運行順序這兩個知識點上很有代表性,代碼如下:

上面這段代碼的運行結(jié)果,我們事先給出來:

首先需要補充一下ES6中關(guān)于async函數(shù)的內(nèi)容。
待完善
(2)CSS盒模型
在前端頁面中,元素會被渲染成一個一個的矩形區(qū)域,而渲染的依據(jù)就是CSS盒模型(basic box model)。每一個盒子由四個部分組成,內(nèi)容區(qū)域、內(nèi)邊距區(qū)域、邊框區(qū)域以及外邊距區(qū)域。

內(nèi)容區(qū)域通常放著元素真實的內(nèi)容,如文本、圖像或者播放器等。
內(nèi)邊距區(qū)域與內(nèi)容區(qū)域相鄰,負責(zé)擴展內(nèi)容區(qū)域的背景,填充在內(nèi)容區(qū)域與邊框之間。
邊框區(qū)域與內(nèi)邊距區(qū)域相鄰,是容納邊框的區(qū)域。
外邊距區(qū)域與邊框區(qū)域相鄰,可以將相鄰的元素相互分隔開(這里時常會出現(xiàn)邊距融合的現(xiàn)象)。
在CSS發(fā)展的過程中,出現(xiàn)了兩種盒模型,W3C標(biāo)準(zhǔn)盒模型以及怪異盒模型。這兩種盒模型的主要差異在于對于渲染的元素塊的尺寸計算不同。
在W3C標(biāo)準(zhǔn)盒模型中,width以及height指定的寬高就是內(nèi)容區(qū)域的實際寬高,真正的盒子寬高計算就變成了
realWidth = width + padding寬度 + border寬度
realHeight = height + padding高度 + border高度
在怪異盒模型中,我們設(shè)置的width/height就是盒子的寬高,真實的內(nèi)容區(qū)域的寬度是width減去padding以及border的寬度后的結(jié)果,高度算法相同。
雖然W3C標(biāo)準(zhǔn)盒模型是公開的標(biāo)準(zhǔn),但是它并不好用,相反微軟堅持使用的IE怪異盒模型在開發(fā)中體驗十分友好。舉一個典型的例子:假設(shè)一行有四個盒子,每一個盒子有1px寬的邊框,按照標(biāo)準(zhǔn)盒模型,每一個盒子給出25%的寬度,四個盒子并不能并行排列在一行,而按照怪異盒模型,每一個盒子的寬度會被設(shè)置為25%,其內(nèi)容寬度則是在25%盒子寬度的基礎(chǔ)上減去2px的邊框?qū)挾群蟮玫降慕Y(jié)果,無論我們?nèi)绾卧O(shè)置邊框?qū)挾榷疾粫绊戜秩緣K的寬度,這就體現(xiàn)了怪異盒模型的好處。這么一看標(biāo)準(zhǔn)也未必都是完美的,所以為了靈活地在兩個模式下切換CSS中提供了box-sizing去設(shè)置border-box屬性值讓我們可以在某一個確定的元素上使用怪異模式去計算寬高。
前文介紹margin外邊距的時候,我們提到了一個外邊距融合的問題,這個現(xiàn)象是怎么產(chǎn)生的呢?我們假設(shè)元素塊A的外邊距是20px,而元素B的外邊距是30px,前面也提到外邊距是用來分隔相鄰元素的,那么我們是不是只要保證當(dāng)前塊和相鄰塊之間的間距大于等于我們設(shè)置的值就可以了,我們給這個值起個名字叫做最小安全距離,所以元素A和元素B之間我們會取到其中一個較大的值,這個值就是最小安全距離。所以我們給出的這個例子最終A和B之間的距離會是30px。
解釋完為什么會出現(xiàn)外邊距融合后,我們還需要了解一點外邊距融合只會在垂直方向上發(fā)生,即marginTop和marginBottom融合,水平方向的margin是不會融合的,大致情況如圖:

根據(jù)MDN的說法,margin的合并大致會有以下幾種場景:
- 相鄰元素之間
- 父元素與第一個以及最后一個元素之間
- 空的塊級元素自身
首先,我們來理一下第一種情況,前文提到過margin合并只會出現(xiàn)在垂直方向,在這里補充一點margin合并還僅發(fā)生在塊級元素上,甚至不包括行內(nèi)塊級元素,所以這里所謂的相鄰指的就是兩個相鄰的塊級元素。
[圖片上傳失敗...(image-129f27-1574575972909)]
第二種情況如下圖所示,當(dāng)我們給子元素設(shè)置上邊距和下邊距時,時常會發(fā)現(xiàn)我們并沒有將子元素和父元素之間擠出一段空隙,反而將父元素一塊向下頂了一段距離出來。


第三種情況就比較抽象了,我繪制了一個示例,如下圖:

實際上第三種情況,簡單理解就是當(dāng)一個元素是空塊(無高度、無內(nèi)容、無border/padding)的時候,它本身的上下margin會合并在一起,上下兩個Margin值誰大取誰。