摘要:打開一個網頁,看到服務器返回給客戶端瀏覽器的各種文件類型圖片構建瀏覽器會遵守一套步驟將文件轉換為樹。因為瀏覽器有渲染線程與引擎線程,為了防止渲染出現不可預期的結果,這兩個線程是互斥的關系。
1. 瀏覽器架構
用戶界面
主進程
內核
渲染引擎
JS 引擎
執行棧
事件觸發線程
消息隊列
微任務
宏任務
網絡異步線程
定時器線程
2. 從輸入 url 到頁面展示的過程 2.1 流程跳轉
是否有緩存
DNS查找,域名解析ip
創建TCP鏈接,之后才有HTTP三次握手(HTTP尋在TCP之上)
發送請求(Request)
接收響應(Response),返回請求的文件 (html)
瀏覽器渲染(1,2并行,后面是串行)
解析HTML --> DOM ree
標記化算法,進行元素狀態的標記
生成DOM
解析CSS --> CSS tree
生成CSSOM
結合 --> Render tree
結合DOM與CSSOM,生成渲染樹(Render tree)
layout: 布局(布局渲染樹)
painting: 繪制(繪制渲染樹)
2.2 參考https://juejin.im/post/5c64d1...
https://juejin.im/book/5b9365...
Cookie 的本職工作并非本地存儲,而是“維持狀態”。
因為HTTP協議是無狀態的,HTTP協議自身不對請求和響應之間的通信狀態進行保存,通俗來說,服務器不知道用戶上一次做了什么,這嚴重阻礙了交互式Web應用程序的實現。
在典型的網上購物場景中,用戶瀏覽了幾個頁面,買了一盒餅干和兩瓶飲料。最后結帳時,由于HTTP的無狀態性,不通過額外的手段,服務器并不知道用戶到底買了什么,于是就誕生了Cookie
我們可以把Cookie 理解為一個存儲在瀏覽器里的一個小小的文本文件,它附著在 HTTP 請求上,在瀏覽器和服務器之間“飛來飛去”。它可以攜帶用戶信息,當服務器檢查 Cookie 的時候,便可以獲取到客戶端的狀態
在剛才的購物場景中,當用戶選購了第一項商品,服務器在向用戶發送網頁的同時,還發送了一段Cookie,記錄著那項商品的信息。當用戶訪問另一個頁面,瀏覽器會把Cookie發送給服務器,于是服務器知道他之前選購了什么。用戶繼續選購飲料,服務器就在原來那段Cookie里追加新的商品信息。結帳時,服務器讀取發送來的Cookie就行了。
3.1.2 什么是cookieCookie指某些網站為了辨別用戶身份而儲存在用戶本地終端上的數據(通常經過加密)。 cookie是服務端生成,客戶端進行維護和存儲
通過cookie,可以讓服務器知道請求是來源哪個客戶端,就可以進行客戶端狀態的維護,比如登陸后刷新,請求頭就會攜帶登陸時response header中的set-cookie,Web服務器接到請求時也能讀出cookie的值,根據cookie值的內容就可以判斷和恢復一些用戶的信息狀態。
記住密碼,下次自動登錄。
購物車功能
記錄用戶瀏覽數據,進行商品(廣告)推薦。
3.1.4 Cookie的原理第一次訪問網站的時候,瀏覽器發出請求,服務器響應請求后,會在響應頭里面添加一個Set-Cookie選項,將cookie放入到響應請求中,在瀏覽器第二次發請求的時候,會通過Cookie請求頭部將Cookie信息發送給服務器,服務端會辨別用戶身份,另外,Cookie的過期時間、域、路徑、有效期、適用站點都可以根據需要來指定。
3.1.5 Cookie生成方式服務端
http response header中的set-cookie
客戶端
js中可以通過document.cookie可以讀寫cookie
document.cookie="userName=hello"3.1.6 Cookie的缺陷
Cookie 不夠大
各瀏覽器的cookie每一個name=value的value值大概在4k,所以4k并不是一個域名下所有的cookie共享的,而是一個name的大小。
過多的 Cookie 會帶來巨大的性能浪費
Cookie 是緊跟域名的。同一個域名下的所有請求,都會攜帶 Cookie
由于在HTTP請求中的Cookie是明文傳遞的,所以安全性成問題,除非用HTTPS。
3.1.7 Cookie與安全屬性 | 作用 |
---|---|
value | 如果用于保存用戶登錄狀態,應該將該值加密,不能使用明文的用戶標識(例如可以使用md5加密) |
http-only | 不能通過js訪問cookie,減少xss攻擊 |
secure | 只能在協議為https的請求中攜帶 |
same-site | 規定瀏覽器不能在跨域的請求中攜帶cookie,減少csrf攻擊 |
保存的數據長期存在,下一次訪問該網站的時候,網頁可以直接讀取以前保存的數據。
大小為5M左右
僅在客戶端使用,不和服務端進行通信
LocalStorage可以作為瀏覽器本地緩存方案,用來提升網頁首屏渲染速度(根據第一請求返回時,將一些不變信息直接存儲在本地)。
3.2.2存入/讀取數據localStorage.setItem("key","value"); var valueLocal = localStorage.getItem("key");3.3sessionStorage 3.3.1 sessionStorage的特點
會話級別的瀏覽器存儲
大小為5M左右
僅在客戶端使用,不和服務端進行通信
3.3.2 窗口共享即便是相同域名下的兩個頁面,只要它們不在同一個瀏覽器窗口(瀏覽器的標簽頁)中打開,那么它們的 sessionStorage 內容便無法共享
localStorage 在所有同源窗口中都是共享的
cookie也是在所有同源窗口中都是共享的
3.3.3存入/讀取數據localStorage.setItem("key","value"); var valueLocal = localStorage.getItem("key");3.4 總結
特性 | cookie | localStorage | sessionStorage | indexDB |
---|---|---|---|---|
數據生命周期 | 一般由服務器生成,可以設置過期時間 | 除非被手動清理,否則一直存在 | 頁面關閉就清理 | 除非被手動清理,否則一直存在 |
數據存儲大小 | 4k | 5M | 5M | 無限 |
與服務端通信 | 每次都會攜帶在header中,對于請求有性能影響 | 不參與 | 不參與 | 不參與 |
cookie 已經不建議用于存儲。如果沒有大量數據存儲需求的話,可以使用 localStorage 和 sessionStorage 。對于不怎么改變的數據盡量使用 localStorage 存儲,否則可以用 sessionStorage 存儲。
Cookie 的本職工作并非本地存儲,而是“維持狀態”
Web Storage 是 HTML5 專門為瀏覽器存儲而提供的數據存儲機制,不與服務端發生通信
IndexedDB 用于客戶端存儲大量結構化數據
3.5 參考https://github.com/ljianshu/B...
4. 瀏覽器渲染原理 4.1 什么是渲染過程簡單來說,渲染引擎根據 HTML 文件描述構建相應的數學模型,調用瀏覽器各個零部件,從而將網頁資源代碼轉換為圖像結果,這個過程就是渲染過程(如下圖)
從這個流程來看,瀏覽器呈現網頁這個過程,宛如一個黑盒。在這個神秘的黑盒中,有許多功能模塊,內核內部的實現正是這些功能模塊相互配合協同工作進行的。其中我們最需要關注的,就是HTML 解釋器、CSS 解釋器、圖層布局計算模塊、視圖繪制模塊與JavaScript 引擎這幾大模塊:
HTML 解釋器:將 HTML 文檔經過詞法分析輸出 DOM 樹。
CSS 解釋器:解析 CSS 文檔, 生成樣式規則。
圖層布局計算模塊:布局計算每個對象的精確位置和大小。
視圖繪制模塊:進行具體節點的圖像繪制,將像素渲染到屏幕上。
JavaScript引擎:編譯執行 Javascript 代碼。
打開一個網頁,看到服務器返回給客戶端(瀏覽器)的各種文件類型
document --> html
stylesheet --> css
script --> js
jpeg --> 圖片
4.2 構建DOM瀏覽器會遵守一套步驟將HTML 文件轉換為 DOM 樹。宏觀上,可以分為幾個步驟
瀏覽器從磁盤或網絡讀取HTML的原始字節,并根據文件的指定編碼(例如 UTF-8)將它們轉換成字符串。
在網絡中傳輸的內容其實都是 0 和 1 這些字節數據。當瀏覽器接收到這些字節數據以后,它會將這些字節數據轉換為字符串,也就是我們寫的代碼。
將字符串轉換成Token,例如:、
等。Token中會標識出當前Token是“開始標簽”或是“結束標簽”亦或是“文本”等信息。
上圖給出了節點之間的關系,例如:“Hello”Token位于“title”開始標簽與“title”結束標簽之間,表明“Hello”Token是“title”Token的子節點。同理“title”Token是“head”Token的子節點。
生成節點對象并構建DOM
事實上,構建DOM的過程中,不是等所有Token都轉換完成后再去生成節點對象,而是一邊生成Token一邊消耗Token來生成節點對象。換句話說,每個Token被生成后,會立刻消耗這個Token創建出節點對象。注意:帶有結束標簽標識的Token不會創建節點對象。
實例
Web page parsing Web page parsing
This is an example Web page.
上面這段HTML會解析成這樣
4.3 構建CSSOMDOM會捕獲頁面的內容,但瀏覽器還需要知道頁面如何展示,所以需要構建CSSOM
構建CSSOM的過程與構建DOM的過程非常相似,當瀏覽器接收到一段CSS,瀏覽器首先要做的是識別出Token,然后構建節點并生成CSSOM。
在這一過程中,瀏覽器會確定下每一個節點的樣式到底是什么,并且這一過程其實是很消耗資源的。因為樣式你可以自行設置給某個節點,也可以通過繼承獲得。在這一過程中,瀏覽器得遞歸 CSSOM 樹,然后確定具體的元素到底是什么樣式
注意:CSS匹配HTML元素是一個相當復雜和有性能問題的事情。所以,DOM樹要小,CSS盡量用id和class,千萬不要過渡層疊下去。
4.3 構建渲染樹當我們生成 DOM 樹和 CSSOM 樹以后,就需要將這兩棵樹組合為渲染樹
在這一過程中,不是簡單的將兩者合并就行了。渲染樹只會包括需要顯示的節點和這些節點的樣式信息,如果某個節點是 display: none 的,那么就不會在渲染樹中顯示。
瀏覽器如果渲染過程中遇到JS文件怎么處理
渲染過程中,如果遇到
4.4 布局與繪制當瀏覽器生成渲染樹以后,就會根據渲染樹來進行布局(也可以叫做回流)。這一階段瀏覽器要做的事情是要弄清楚各個節點在頁面中的確切位置和大小。通常這一行為也被稱為“自動重排”。
布局流程的輸出是一個“盒模型”,它會精確地捕獲每個元素在視口內的確切位置和尺寸,所有相對測量值都將轉換為屏幕上的絕對像素。
布局完成后,瀏覽器會立即發出“Paint Setup”和“Paint”事件,將渲染樹轉換成屏幕上的像素。
4.5 async和defer的作用是什么?有什么區別?async 異步
defer 延緩
其中藍色線代表JavaScript加載;紅色線代表JavaScript執行;綠色線代表 HTML 解析。
情況1
沒有 defer 或 async,瀏覽器會立即加載并執行指定的腳本,也就是說不等待后續載入的文檔元素,讀到就加載并執行。
情況2 (異步下載)
async 屬性表示異步執行引入的 JavaScript,與 defer 的區別在于,如果已經加載好(符合異步),就會開始執行——無論此刻是 HTML 解析階段還是 DOMContentLoaded 觸發之后。需要注意的是,這種方式加載的 JavaScript 依然會阻塞 load 事件。換句話說,async-script 可能在 DOMContentLoaded 觸發之前或之后執行,但一定在 load 觸發之前執行
情況3 (延遲執行)
defer 屬性表示延遲執行引入的 JavaScript,即這段 JavaScript 加載時 HTML 并未停止解析,這兩個過程是并行的。整個 document 解析完畢且 defer-script 也加載完成之后(這兩件事情的順序無關),會執行所有由 defer-script 加載的 JavaScript 代碼,然后觸發 DOMContentLoaded 事件。
defer 與相比普通 script,有兩點區別:載入 JavaScript 文件時不阻塞 HTML 的解析,執行階段被放到 HTML 標簽解析完成之后。
在加載多個JS腳本的時候,async是無順序的加載,而defer是有順序的加載。
4.6 為什么操作 DOM 慢把 DOM 和 JavaScript 各自想象成一個島嶼,它們之間用收費橋梁連接。——《高性能 JavaScript》
JS 是很快的,在 JS 中修改 DOM 對象也是很快的。在JS的世界里,一切是簡單的、迅速的。但 DOM 操作并非 JS 一個人的獨舞,而是兩個模塊之間的協作。
因為 DOM 是屬于渲染引擎中的東西,而 JS 又是 JS 引擎中的東西。當我們用 JS 去操作 DOM 時,本質上是 JS 引擎和渲染引擎之間進行了“跨界交流”。這個“跨界交流”的實現并不簡單,它依賴了橋接接口作為“橋梁”(如下圖)。
過“橋”要收費——這個開銷本身就是不可忽略的。我們每操作一次 DOM(不管是為了修改還是僅僅為了訪問其值),都要過一次“橋”。過“橋”的次數一多,就會產生比較明顯的性能問題。因此“減少 DOM 操作”的建議,并非空穴來風。
4.7 總結瀏覽器工作流程:構建DOM -> 構建CSSOM -> 構建渲染樹 -> 布局 -> 繪制。
CSSOM會阻塞渲染,只有當CSSOM構建完畢后才會進入下一個階段構建渲染樹。
通常情況下DOM和CSSOM是并行構建的,但是當瀏覽器遇到一個不帶defer或async屬性的script標簽時,DOM構建將暫停,如果此時又恰巧瀏覽器尚未完成CSSOM的下載和構建,由于JavaScript可以修改CSSOM,所以需要等CSSOM構建完畢后再執行JS,最后才重新DOM構建。
4.8 參考深入淺出瀏覽器渲染原理
5. 重繪與回流(重排) 5.1 名詞解析通俗理解
重繪: 重新繪制,繪制色彩
回流(重排): 重新排版,排版位置大小
比較官方的理解
重繪:當我們對 DOM 的修改導致了樣式的變化、卻并未影響其幾何屬性(比如修改了顏色或背景色)時,瀏覽器不需重新計算元素的幾何屬性、直接為該元素繪制新的樣式(跳過了上圖所示的回流環節)。
回流: 當我們對 DOM 的修改引發了 DOM 幾何尺寸的變化(比如修改元素的寬、高或隱藏元素等)時,瀏覽器需要重新計算元素的幾何屬性(其他元素的幾何屬性和位置也會因此受到影響),然后再將計算的結果繪制出來。這個過程就是回流(也叫重排)
5.2 瀏覽器渲染的流程中的回流與重繪計算CSS樣式
構建RenderTree
Layout(布局) –-> 定位坐標和大小
paint(繪制)-->正式開畫
注意:上圖流程中有很多連接線,這表示了Javascript動態修改了DOM屬性或是CSS屬性會導致重新Layout,但有些改變不會重新Layout,就是上圖中那些指到天上的箭頭,比如修改后的CSS rule沒有被匹配到元素。
我們知道,當網頁生成的時候,至少會渲染一次。在用戶訪問的過程中,還會不斷重新渲染。重新渲染會重復回流+重繪或者只有重繪
回流必定會發生重繪,重繪不一定會引發回流。重繪和回流會在我們設置節點樣式時頻繁出現,同時也會很大程度上影響性能。回流所需的成本比重繪高的多,改變父節點里的子節點很可能會導致父節點的一系列回流。
5.3 常見引起回流屬性和方法任何會改變元素幾何信息(元素的位置和尺寸大小)的操作,都會觸發回流,
添加或者刪除可見的DOM元素;
元素尺寸改變——邊距、填充、邊框、寬度和高度
內容變化,比如用戶在input框中輸入文字
瀏覽器窗口尺寸改變——resize事件發生時
計算 offsetWidth 和 offsetHeight 屬性
設置 style 屬性的值
5.4 常見引起重繪屬性和方法文字屬性
邊框樣式(非大小)
色彩
背景色
陰影
5.5如何減少回流、重繪使用 transform 替代 top
使用 visibility 替換 display: none ,因為前者只會引起重繪,后者會引發回流(改變了布局)
不要把節點的屬性值放在一個循環里當成循環里的變量。
for(let i = 0; i < 1000; i++) { // 獲取 offsetTop 會導致回流,因為需要去獲取正確的值 console.log(document.querySelector(".test").style.offsetTop) }
不要使用 table 布局,可能很小的一個小改動會造成整個 table 的重新布局
動畫實現的速度的選擇,動畫速度越快,回流次數越多,也可以選擇使用 requestAnimationFrame
CSS 選擇符從右往左匹配查找,避免節點層級過多
將頻繁重繪或者回流的節點設置為圖層,圖層能夠阻止該節點的渲染行為影響別的節點。比如對于 video 標簽來說,瀏覽器會自動將該節點變為圖層。
5.6 參考https://github.com/ljianshu/B...
6. 瀏覽器下的Event Loop 6.1 線程與進程 6.1.1概念我們經常說JS 是單線程執行的,指的是一個進程里只有一個主線程,那到底什么是線程?什么是進程?
官方的說法是
進程: CPU資源分配的最小單位
線程: CPU調度的最小單位
進程好比圖中的工廠,有多帶帶的專屬自己的工廠資源。
線程好比圖中的工人,多個工人在一個工廠中協作工作,工廠與工人是 1:n的關系。也就是說一個進程由一個或多個線程組成,線程是一個進程中代碼的不同執行路線;
廠的空間是工人們共享的,這象征一個進程的內存空間是共享的,每個線程都可用這些共享內存。
多個工廠之間獨立存在
6.1.2 多進程與多線程多進程:在同一個時間里,同一個計算機系統中如果允許兩個或兩個以上的進程處于運行狀態。多進程帶來的好處是明顯的,比如你可以聽歌的同時,打開編輯器敲代碼,編輯器和聽歌軟件的進程之間絲毫不會相互干擾。
多線程:程序中包含多個執行流,即在一個程序中可以同時運行多個不同的線程來執行不同的任務,也就是說允許單個程序創建多個并行執行的線程來完成各自的任務。
以Chrome瀏覽器中為例,當你打開一個 Tab 頁時,其實就是創建了一個進程,一個進程中可以有多個線程(下文會詳細介紹),比如渲染線程、JS 引擎線程、HTTP 請求線程等等。當你發起一個請求時,其實就是創建了一個線程,當請求結束后,該線程可能就會被銷毀。
6.1.3js單線程存在的問題js是單線程的,處理任務是一件接著一件處理,所以如果一個任務需要處理很久的話,后面的任務就會被阻塞
所以js通過Event Loop事件循環的方式解決了這個問題
簡單來說瀏覽器內核是通過取得頁面內容、整理信息(應用CSS)、計算和組合最終輸出可視化的圖像結果,通常也被稱為渲染引擎。
瀏覽器內核是多線程,在內核控制下各線程相互配合以保持同步,一個瀏覽器通常由以下常駐線程組成:
GUI 渲染線程
JavaScript引擎線程
定時觸發器線程
事件觸發線程
異步http請求線程
6.2.2GUI渲染線程主要負責頁面的渲染,解析HTML、CSS,構建DOM樹,布局和繪制等。
當界面需要重繪或者由于某種操作引發回流時,將執行該線程。
該線程與JS引擎線程互斥,當執行JS引擎線程時,GUI渲染會被掛起,當任務隊列空閑時,主線程才會去執行GUI渲染。
6.2.3 JS引擎線程該線程當然是主要負責處理 JavaScript腳本,執行代碼。
也是主要負責執行準備好待執行的事件,即定時器計數結束,或者異步請求成功并正確返回時,將依次進入任務隊列,等待 JS引擎線程的執行。
當然,該線程與 GUI渲染線程互斥,當 JS引擎線程執行 JavaScript腳本時間過長,將導致頁面渲染的阻塞。
6.2.4 定時器觸發線程負責執行異步定時器一類的函數的線程,如: setTimeout,setInterval。
主線程依次執行代碼時,遇到定時器,會將定時器交給該線程處理,當計數完畢后,事件觸發線程會將計數完畢后的事件加入到任務隊列的尾部,等待JS引擎線程執行。
6.2.5 事件觸發線程主要負責將準備好的事件交給 JS引擎線程執行
比如 setTimeout定時器計數結束, ajax等異步請求成功并觸發回調函數,或者用戶觸發點擊事件時,該線程會將整裝待發的事件依次加入到任務隊列的隊尾,等待 JS引擎線程的執行。
負責執行異步請求一類的函數的線程,如: Promise,axios,ajax等。
主線程依次執行代碼時,遇到異步請求,會將函數交給該線程處理,當監聽到狀態碼變更,如果有回調函數,事件觸發線程會將回調函數加入到任務隊列的尾部,等待JS引擎線程執行。
6.3 event loop 6.3.1stack,queue,heapstack(棧),先進后出
queue(隊列),先進先出,生活中的排隊
heap(堆):存儲obj對象
6.3.2 執行棧js引擎運行時,當代碼開始運行的時候,會將代碼,壓入執行棧進行執行
實例
當代碼被解析后,函數會依次被壓入到棧中
有入棧,就要有出棧,當函數c執行完,開始出棧
前面執行棧,先入后出,但其實也是同步的,同步就意味著會阻塞,所以需要異步,那當執行棧中出現異步代碼會怎么樣
當瀏覽器在執行棧執行的時候,發現有異步任務之后,會交給webapi去維護,而執行棧則繼續執行后面的任務
同樣,setTimeout同樣會被添加到webapi中
webapi是瀏覽器自己實現的功能,這里專門維護事件。
上面setTimeout旁邊有個進度條,這個進度就是設置的等待時間
6.3.4 回調隊列callback queue當setTimeout執行結束的時候,是不是就應該回到執行棧,進行執行輸出呢?
答案:并不是!
此時,倒計時結束后的setTimeout的可執行函數,被放入了回調隊列
最后,setTimeout的可執行函數,被從回調隊列中取出,再次放入了執行棧
這樣的執行過程就叫 event loop事件循環
6.4 Event Loop的具體流程 6.4.1 執行棧任務清空后,才會從回調隊列頭部取出一個任務console.log(1)被壓入執行棧
setTimeout在執行棧被識別為異步任務,放入webapis中
console.log(3)被壓入執行棧,此時setTimeout倒計時結束后,把可執行代碼console.log(2)放入回調隊列里等待
console.log(3)執行完成后,從回調隊列頭部取出console.log(2),放入執行棧
console.log(2)執行
6.4.2 回調隊列先進先出當console.log(4)執行完成后,從回調隊列里取出了console.log(2);
只有console.log(2)執行完成,執行棧再次清空時,才會從回調隊列取出console.log(3)一個一個拿,先拿console.log(2),執行完后,再拿console.log(3),執行
6.4.3 把代碼從回調隊列拿到棧中執行,發現在這段代碼中有異步輸出1,將2push進回調隊列
將4push進回調隊列
輸出5
清空了執行棧,讀取輸出2(從回調隊列中放到棧中執行),發現有3(異步),將3push進回調隊列
清空了執行棧,讀取輸出4
清空了執行棧,讀取輸出3
5.4 Macrotask(宏任務)、Microtask(微任務) 5.4.1 什么是宏任務,微任務宏任務: setTimeout、setInterval、script(整體代碼)、 I/O 操作、UI 渲染等
微任務: new Promise().then(回調)、MutationObserver(html5新特性)、process.nextTick、Object.observe 等。
Microtask(微任務)同樣是一個任務隊列,這個隊列的執行順序是在清空執行棧之后
可以看到Macrotask(宏任務)也就是回調隊列上面還有一個Microtask(微任務)
Microtask(微任務)雖然是隊列,但并不是一個一個放入執行棧,而是當執行棧請空,會執行全部Microtask(微任務)隊列中的任務,最后才是取回調隊列的第一個Macrotask(宏任務)
將setTimeout給push進宏任務
將then(2)push進微任務
將then(4)push進微任務
任務隊列為空,取出微任務第一個then(2)壓入執行棧
輸出2,將then(3)push進微任務
任務隊列為空,取出微任務第一個then(4)壓入執行棧
輸出4
任務隊列為空,取出微任務第一個then(3)壓入執行棧
輸出3
任務隊列為空,微任務也為空,取出宏任務中的setTimeout(1)
輸出1
實例
console.log("1"); setTimeout(()=>{ console.log(2) Promise.resolve().then(()=>{ console.log(3); process.nextTick(function foo() { console.log(4); }); }) }) Promise.resolve().then(()=>{ console.log(5); setTimeout(()=>{ console.log(6) }) Promise.resolve().then(()=>{ console.log(7); }) }) process.nextTick(function foo() { console.log(8); process.nextTick(function foo() { console.log(9); }); }); console.log("10")
1,輸出1
2,將setTimeout(2)push進宏任務
3,將then(5)push進微任務
4,在執行棧底部添加nextTick(8)
5,輸出10
6,執行nextTick(8)
7,輸出8
8,在執行棧底部添加nextTick(9)
9,輸出9
10,執行微任務then(5)
11,輸出5
12,將setTimeout(6)push進宏任務
13,將then(7)push進微任務
14,執行微任務then(7)
15,輸出7
16,取出setTimeout(2)
17,輸出2
18,將then(3)push進微任務
19,執行微任務then(3)
20,輸出3
21,在執行棧底部添加nextTick(4)
22,輸出4
23,取出setTimeout(6)
24,輸出6
https://juejin.im/post/5a6309...
https://github.com/ljianshu/B...
程序的運行需要內存。只要程序提出要求,操作系統或者運行時就必須供給內存
所謂的內存泄漏簡單來說是不再用到的內存,沒有及時釋放
Javascript具有自動垃圾回收機制(Garbage Collecation)。
由于字符串、對象和數組沒有固定大小,所有當他們的大小已知時,才能對他們進行動態的存儲分配。JavaScript程序每次創建字符串、數組或對象時,解釋器都必須分配內存來存儲那個實體。只要像這樣動態地分配了內存,最終都要釋放這些內存以便他們能夠被再用,否則,JavaScript的解釋器將會消耗完系統中所有可用的內存,造成系統崩潰。
最簡單的垃圾回收
JavaScript垃圾回收的機制很簡單:找出不再使用的變量,然后釋放掉其占用的內存,但是這個過程不是時時的,因為其開銷比較大,所以垃圾回收器會按照固定的時間間隔周期性的執行。
var a = "浪里行舟"; var b = "前端工匠"; var a = b; //重寫a
這段代碼運行之后,“浪里行舟”這個字符串失去了引用(之前是被a引用),系統檢測到這個事實之后,就會釋放該字符串的存儲空間以便這些空間可以被再利用。
6.3 垃圾回收機制垃圾回收有兩種方法:標記清除、引用計數。引用計數不太常用,標記清除較為常用。
標記清除這是javascript中最常用的垃圾回收方式。當變量進入執行環境是,就標記這個變量為“進入環境”。從邏輯上講,永遠不能釋放進入環境的變量所占用的內存,因為只要執行流進入相應的環境,就可能會用到他們。當變量離開環境時,則將其標記為“離開環境”。
function addTen(num){ var sum += num; //垃圾收集已將這個變量標記為“進入環境”。 return sum; //垃圾收集已將這個變量標記為“離開環境”。 } addTen(10); //輸出20
var user = {name : "scott", age : "21", gender : "male"}; //在全局中定義變量,標記變量為“進入環境” user = null; //最后定義為null,釋放內存
var m = 0,n = 19 // 把 m,n,add() 標記為進入環境。 add(m, n) // 把 a, b,標記為進入環境。 console.log(n) // n標記為離開環境,等待垃圾回收。 function add(a, b) { a++ var c = a + b //c標記為進入環境 return c //c標記離開環境 }6.4 哪些情況會引起內存泄漏
雖然JavaScript會自動垃圾收集,但是如果我們的代碼寫法不當,會讓變量一直處于“進入環境”的狀態,無法被回收
6.4.1 意外的全局變量function foo(arg) { bar = "this is a hidden global variable"; }
bar沒被聲明,會變成一個全局變量,在頁面關閉之前不會被釋放。
另一種意外的全局變量可能由 this 創建:
function foo() { this.variable = "potential accidental global"; } // foo 調用自己,this 指向了全局對象(window) foo();
在 JavaScript 文件頭部加上 "use strict",可以避免此類錯誤發生。啟用嚴格模式解析 JavaScript ,避免意外的全局變量。
6.4.2 被遺忘的計時器或回調函數var someResource = getData(); setInterval(function() { var node = document.getElementById("Node"); if(node) { // 處理 node 和 someResource node.innerHTML = JSON.stringify(someResource)); } }, 1000);
如果id為Node的元素從DOM中移除,該定時器仍會存在,同時,因為回調函數中包含對someResource的引用,定時器外面的someResource也不會被釋放。
6.4.3 閉包function bindEvent(){ var obj=document.createElement("xxx") obj.onclick=function(){ // Even if it is a empty function } }
閉包可以維持函數內局部變量,使其得不到釋放。上例定義事件回調時,由于是函數內定義函數,并且內部函數--事件回調引用外部函數,形成了閉包。
// 將事件處理函數定義在外面 function bindEvent() { var obj = document.createElement("xxx") obj.onclick = onclickHandler } // 或者在定義事件處理函數的外部函數中,刪除對dom的引用 function bindEvent() { var obj = document.createElement("xxx") obj.onclick = function() { // Even if it is a empty function } obj = null }6.4.4 沒有清理的DOM元素引用
有時,保存 DOM 節點內部數據結構很有用。假如你想快速更新表格的幾行內容,把每一行 DOM 存成字典(JSON 鍵值對)或者數組很有意義。此時,同樣的 DOM 元素存在兩個引用:一個在 DOM 樹中,另一個在字典中。將來你決定刪除這些行時,需要把兩個引用都清除。
var elements = { button: document.getElementById("button"), image: document.getElementById("image"), text: document.getElementById("text") }; function doStuff() { image.src = "http://some.url/image"; button.click(); console.log(text.innerHTML); } function removeButton() { document.body.removeChild(document.getElementById("button")); // 此時,仍舊存在一個全局的 #button 的引用 // elements 字典。button 元素仍舊在內存中,不能被 GC 回收。 }
雖然我們用removeChild移除了button,但是還在elements對象里保存著#button的引用,換言之,DOM元素還在內存里面。
6.5 內存泄漏的識別方法步驟
打開開發者工具 Performance
勾選 Screenshots 和 memory
左上角小圓點開始錄制(record)
停止錄制
圖中 Heap 對應的部分就可以看到內存在周期性的回落也可以看到垃圾回收的周期,如果垃圾回收之后的最低值(我們稱為min),min在不斷上漲,那么肯定是有較為嚴重的內存泄漏問題。
避免內存泄漏的一些方式
減少不必要的全局變量,或者生命周期較長的對象,及時對無用的數據進行垃圾回收
注意程序邏輯,避免“死循環”之類的
避免創建過多的對象
不用了的東西要及時歸還
6.6 垃圾回收的使用場景優化 6.6.1 數組array優化[]賦值給一個數組對象,是清空數組的捷徑(例如: arr = [];),但是需要注意的是,這種方式又創建了一個新的空對象,并且將原來的數組對象變成了一小片內存垃圾!實際上,將數組長度賦值為0(arr.length = 0)也能達到清空數組的目的,并且同時能實現數組重用,減少內存垃圾的產生。
const arr = [1, 2, 3, 4]; console.log("浪里行舟"); arr.length = 0 // 可以直接讓數字清空,而且數組類型不變。 // arr = []; 雖然讓a變量成一個空數組,但是在堆上重新申請了一個空數組對象。6.6.2 對象盡量復用
對象盡量復用,尤其是在循環等地方出現創建新對象,能復用就復用。不用的對象,盡可能設置為null,盡快被垃圾回收掉。
var t = {} // 每次循環都會創建一個新對象。 for (var i = 0; i < 10; i++) { // var t = {};// 每次循環都會創建一個新對象。 t.age = 19 t.name = "123" t.index = i console.log(t) } t = null //對象如果已經不用了,那就立即設置為null;等待垃圾回收。6.6.3 在循環中的函數表達式,能復用最好放到循環外面
// 在循環中最好也別使用函數表達式。 for (var k = 0; k < 10; k++) { var t = function(a) { // 創建了10次 函數對象。 console.log(a) } t(k) }
// 推薦用法 function t(a) { console.log(a) } for (var k = 0; k < 10; k++) { t(k) } t = null6.7 參考
https://github.com/ljianshu/B...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/104189.html
摘要:適當引導面試官。如果有機會來實習,如何最有效的快速成長淘寶技術部前端內部有針對新同學的前端夜校,有專門的老師授課。 阿里巴巴2019前端實習生招聘還剩最后兩周,面向2019年11月1日至2020年10月31日之間畢業的同學,在這里分享下阿里前端面試考核的關鍵點: Q:在面試過程中,前端面試官如何考核面試者?A:會看同學為什么選擇前端行業?是因為算法太難?Java、C++太難?還是因為熱...
摘要:閉包有多重前端知識點大百科全書前端掘金,,技巧使你的更加專業前端掘金一個幫你提升技巧的收藏集。 Vue全家桶實現還原豆瓣電影wap版 - 掘金用vue全家桶仿寫豆瓣電影wap版。 最近在公司項目中嘗試使用vue,但奈何自己初學水平有限,上了vue沒有上vuex,開發過程特別難受。 于是玩一玩本項目,算是對相關技術更加熟悉了。 原計劃仿寫完所有頁面,礙于豆瓣的接口API有限,實現頁面也有...
摘要:計算數組的極值微信面試題獲取元素的最終前端掘金一題目用代碼求出頁面上一個元素的最終的,不考慮瀏覽器,不考慮元素情況。 Excuse me?這個前端面試在搞事! - 前端 - 掘金金三銀四搞事季,前端這個近年的熱門領域,搞事氣氛特別強烈,我朋友小偉最近就在瘋狂面試,遇到了許多有趣的面試官,有趣的面試題,我來幫這個搞事 boy 轉述一下。 以下是我一個朋友的故事,真的不是我。 ... ja...
摘要:知識點前端面試有很多知識點,因為前端本就涉及到多個方面。因為對于這樣的前端框架我還不是很熟練,在這方面不能提供很好的學習思路。 關于這幾次的面試 前幾次的面試,讓我對于一個前端工程師需要掌握的知識體系有了一個全新的認識。之前自己在學習方面一直屬于野路子,沒有一個很規范的學習路徑,往往都是想到什么就去學什么。而且基本都是處于會用的那種水平。并沒有真正的做到知其然且知其所以然。面試基本都沒...
閱讀 3773·2021-11-23 09:51
閱讀 4386·2021-11-15 11:37
閱讀 3523·2021-09-02 15:21
閱讀 2746·2021-09-01 10:31
閱讀 879·2021-08-31 14:19
閱讀 852·2021-08-11 11:20
閱讀 3308·2021-07-30 15:30
閱讀 1689·2019-08-30 15:54