廢話只說一句:碼字不易求個(gè)??,收藏 === 學(xué)會(huì),快行動(dòng)起來吧!?????????。2022.05.19
由于篇幅限制更詳細(xì)的內(nèi)容已更新到 ? 我的 GitHub 上,有糾正錯(cuò)誤和需要補(bǔ)充的小伙伴可以在這里留言,我會(huì)及時(shí)更新上去的。推薦個(gè)API管理神器 Apipost 用過的都知道好使
1. HTTP 和 HTTPS
1.http 和 https 的基本概念
http: 是一個(gè)客戶端和服務(wù)器端請(qǐng)求和應(yīng)答的標(biāo)準(zhǔn)(TCP),用于從 WWW 服務(wù)器傳輸超文本到本地瀏覽器的超文本傳輸協(xié)議。
https:是以安全為目標(biāo)的 HTTP 通道,即 HTTP 下 加入 SSL 層進(jìn)行加密。其作用是:建立一個(gè)信息安全通道,來確保數(shù)據(jù)的傳輸,確保網(wǎng)站的真實(shí)性。
2.http 和 https 的區(qū)別及優(yōu)缺點(diǎn)?
- http 是超文本傳輸協(xié)議,信息是明文傳輸,HTTPS 協(xié)議要比 http 協(xié)議
安全 ,https 是具有安全性的 ssl 加密傳輸協(xié)議,可防止數(shù)據(jù)在傳輸過程中被竊取、改變,確保數(shù)據(jù)的完整性(當(dāng)然這種安全性并非絕對(duì)的,對(duì)于更深入的 Web 安全問題,此處暫且不表)。
- http 協(xié)議的
默認(rèn)端口 為 80,https 的默認(rèn)端口為 443。
- http 的連接很簡單,是無狀態(tài)的。https 握手階段比較
費(fèi)時(shí) ,會(huì)使頁面加載時(shí)間延長 50%,增加 10%~20%的耗電。
- https
緩存 不如 http 高效,會(huì)增加數(shù)據(jù)開銷。
- Https 協(xié)議需要 ca 證書,費(fèi)用較高,功能越強(qiáng)大的
證書費(fèi) 用越高。
- SSL 證書需要綁定
IP ,不能再同一個(gè) IP 上綁定多個(gè)域名,IPV4 資源支持不了這種消耗。
3.https 協(xié)議的工作原理
客戶端在使用 HTTPS 方式與 Web 服務(wù)器通信時(shí)有以下幾個(gè)步驟:
- 客戶端使用 https url 訪問服務(wù)器,則要求 web 服務(wù)器
建立 ssl 鏈接 。
- web 服務(wù)器接收到客戶端的請(qǐng)求之后,會(huì)
將網(wǎng)站的證書(證書中包含了公鑰),傳輸給客戶端 。
- 客戶端和 web 服務(wù)器端開始
協(xié)商 SSL 鏈接的安全等級(jí) ,也就是加密等級(jí)。
- 客戶端瀏覽器通過雙方協(xié)商一致的安全等級(jí),
建立會(huì)話密鑰 ,然后通過網(wǎng)站的公鑰來加密會(huì)話密鑰,并傳送給網(wǎng)站。
- web 服務(wù)器
通過自己的私鑰解密出會(huì)話密鑰 。
- web 服務(wù)器
通過會(huì)話密鑰加密與客戶端之間的通信 。
傳送門 ? # 解讀 HTTP1/HTTP2/HTTP3
TCP三次握手
- 第一次握手:
建立連接時(shí),客戶端發(fā)送syn包(syn=j)到服務(wù)器,并進(jìn)入SYN_SENT狀態(tài),等待服務(wù)器確認(rèn) ;SYN:同步序列編號(hào)(Synchronize Sequence Numbers)。
- 第二次握手:
服務(wù)器收到syn包并確認(rèn)客戶的SYN (ack=j+1),同時(shí)也發(fā)送一個(gè)自己的SYN包 (syn=k),即SYN+ACK包,此時(shí)服務(wù)器進(jìn)入SYN_RECV狀態(tài);
- 第三次握手:
客戶端收到服務(wù)器的SYN+ACK包,向服務(wù)器發(fā)送確認(rèn)包ACK(ack=k+1) ,此包發(fā)送完畢,客戶端和服務(wù)器進(jìn)入ESTABLISHED(TCP連接成功)狀態(tài),完成三次握手。
握手過程中傳送的包里不包含數(shù)據(jù),三次握手完畢后,客戶端與服務(wù)器才正式開始傳送數(shù)據(jù)。
TCP 四次揮手
客戶端進(jìn)程發(fā)出連接釋放報(bào)文 ,并且停止發(fā)送數(shù)據(jù)。釋放數(shù)據(jù)報(bào)文首部,F(xiàn)IN=1,其序列號(hào)為seq=u(等于前面已經(jīng)傳送過來的數(shù)據(jù)的最后一個(gè)字節(jié)的序號(hào)加1),此時(shí),客戶端進(jìn)入FIN-WAIT-1(終止等待1)狀態(tài) 。 TCP規(guī)定,F(xiàn)IN報(bào)文段即使不攜帶數(shù)據(jù),也要消耗一個(gè)序號(hào)。
2)服務(wù)器收到連接釋放報(bào)文,發(fā)出確認(rèn)報(bào)文 ,ACK=1,ack=u+1,并且?guī)献约旱男蛄刑?hào)seq=v,此時(shí),服務(wù)端就進(jìn)入了CLOSE-WAIT(關(guān)閉等待)狀態(tài) 。TCP服務(wù)器通知高層的應(yīng)用進(jìn)程,客戶端向服務(wù)器的方向就釋放了,這時(shí)候處于半關(guān)閉狀態(tài),即客戶端已經(jīng)沒有數(shù)據(jù)要發(fā)送了,但是服務(wù)器若發(fā)送數(shù)據(jù),客戶端依然要接受。這個(gè)狀態(tài)還要持續(xù)一段時(shí)間,也就是整個(gè)CLOSE-WAIT狀態(tài)持續(xù)的時(shí)間。
3)客戶端收到服務(wù)器的確認(rèn)請(qǐng)求后,此時(shí),客戶端就進(jìn)入FIN-WAIT-2(終止等待2)狀態(tài) ,等待服務(wù)器發(fā)送連接釋放報(bào)文(在這之前還需要接受服務(wù)器發(fā)送的最 后的數(shù)據(jù))。
4)服務(wù)器將最后的數(shù)據(jù)發(fā)送完畢后,就向客戶端發(fā)送連接釋放報(bào)文 ,F(xiàn)IN=1,ack=u+1,由于在半關(guān)閉狀態(tài),服務(wù)器很可能又發(fā)送了一些數(shù)據(jù),假定此時(shí)的序列號(hào)為seq=w,此時(shí),服務(wù)器就進(jìn)入了LAST-ACK(最后確認(rèn))狀態(tài) ,等待客戶端的確認(rèn)。
5)客戶端收到服務(wù)器的連接釋放報(bào)文后,必須發(fā)出確認(rèn) ,ACK=1,ack=w+1,而自己的序列號(hào)是seq=u+1,此時(shí),客戶端就進(jìn)入了TIME-WAIT(時(shí)間等待)狀態(tài) 。注意此時(shí)TCP連接還沒有釋放,必須經(jīng)過2??MSL(最長報(bào)文段壽命)的時(shí)間后,當(dāng)客戶端撤銷相應(yīng)的TCB后,才進(jìn)入CLOSED狀態(tài) 。
6)服務(wù)器只要收到了客戶端發(fā)出的確認(rèn),立即進(jìn)入CLOSED狀態(tài) 。同樣,撤銷TCB后,就結(jié)束了這次的TCP連接??梢钥吹?,服務(wù)器結(jié)束TCP連接的時(shí)間要比客戶端早一些 。
TCP/IP / 如何保證數(shù)據(jù)包傳輸?shù)挠行蚩煽浚?/h3>
對(duì)字節(jié)流分段并進(jìn)行編號(hào)然后通過 ACK 回復(fù) 和超時(shí)重發(fā) 這兩個(gè)機(jī)制來保證。
(1)為了保證數(shù)據(jù)包的可靠傳遞,發(fā)送方必須把已發(fā)送的數(shù)據(jù)包保留在緩沖區(qū);
(2)并為每個(gè)已發(fā)送的數(shù)據(jù)包啟動(dòng)一個(gè)超時(shí)定時(shí)器;
(3)如在定時(shí)器超時(shí)之前收到了對(duì)方發(fā)來的應(yīng)答信息(可能是對(duì)本包的應(yīng)答,也可以是對(duì)本包后續(xù)包的應(yīng)答),則釋放該數(shù)據(jù)包占用的緩沖區(qū);
(4)否則,重傳該數(shù)據(jù)包,直到收到應(yīng)答或重傳次數(shù)超過規(guī)定的最大次數(shù)為止。
(5)接收方收到數(shù)據(jù)包后,先進(jìn)行CRC校驗(yàn),如果正確則把數(shù)據(jù)交給上層協(xié)議,然后給發(fā)送方發(fā)送一個(gè)累計(jì)應(yīng)答包,表明該數(shù)據(jù)已收到,如果接收方正好也有數(shù)據(jù)要發(fā)給發(fā)送方,應(yīng)答包也可方在數(shù)據(jù)包中捎帶過去。
TCP和UDP的區(qū)別
- TCP是面向
鏈接 的,而UDP是面向無連接的。
- TCP僅支持
單播傳輸 ,UDP 提供了單播,多播,廣播的功能。
- TCP的三次握手保證了連接的
可靠性 ; UDP是無連接的、不可靠的一種數(shù)據(jù)傳輸協(xié)議,首先不可靠性體現(xiàn)在無連接上,通信都不需要建立連接,對(duì)接收到的數(shù)據(jù)也不發(fā)送確認(rèn)信號(hào),發(fā)送端不知道數(shù)據(jù)是否會(huì)正確接收。
- UDP的
頭部開銷 比TCP的更小,數(shù)據(jù)傳輸速率更高 ,實(shí)時(shí)性更好 。
傳送門 ? # 深度剖析TCP與UDP的區(qū)別
HTTP 請(qǐng)求跨域問題
-
跨域的原理
跨域,是指瀏覽器不能執(zhí)行其他網(wǎng)站的腳本。它是由瀏覽器的同源策略 造成的。
同源策略,是瀏覽器對(duì) JavaScript 實(shí)施的安全限制,只要協(xié)議、域名、端口 有任何一個(gè)不同,都被當(dāng)作是不同的域。
跨域原理,即是通過各種方式,避開瀏覽器的安全限制 。
-
解決方案
最初做項(xiàng)目的時(shí)候,使用的是jsonp,但存在一些問題,使用get請(qǐng)求不安全,攜帶數(shù)據(jù)較小,后來也用過iframe,但只有主域相同才行,也是存在些問題,后來通過了解和學(xué)習(xí)發(fā)現(xiàn)使用代理和proxy代理配合起來使用比較方便,就引導(dǎo)后臺(tái)按這種方式做下服務(wù)器配置,在開發(fā)中使用proxy,在服務(wù)器上使用nginx代理,這樣開發(fā)過程中彼此都方便,效率也高;現(xiàn)在h5新特性還有 windows.postMessage()
-
JSONP:
ajax 請(qǐng)求受同源策略影響,不允許進(jìn)行跨域請(qǐng)求,而 script 標(biāo)簽 src 屬性中的鏈 接卻可以訪問跨域的 js 腳本,利用這個(gè)特性,服務(wù)端不再返回 JSON 格式的數(shù)據(jù),而是 返回一段調(diào)用某個(gè)函數(shù)的 js 代碼,在 src 中進(jìn)行了調(diào)用,這樣實(shí)現(xiàn)了跨域。
步驟:
- 去創(chuàng)建一個(gè)script標(biāo)簽
- script的src屬性設(shè)置接口地址
- 接口參數(shù),必須要帶一個(gè)自定義函數(shù)名,要不然后臺(tái)無法返回?cái)?shù)據(jù)
- 通過定義函數(shù)名去接受返回的數(shù)據(jù)
//動(dòng)態(tài)創(chuàng)建 script
var script = document.createElement('script');
// 設(shè)置回調(diào)函數(shù)
function getData(data) {
console.log(data);
}
//設(shè)置 script 的 src 屬性,并設(shè)置請(qǐng)求地址
script.src = 'http://localhost:3000/?callback=getData';
// 讓 script 生效
document.body.appendChild(script);
復(fù)制代碼
JSONP 的缺點(diǎn):
JSON 只支持 get,因?yàn)?script 標(biāo)簽只能使用 get 請(qǐng)求; JSONP 需要后端配合返回指定格式的數(shù)據(jù)。
-
document.domain 基礎(chǔ)域名相同 子域名不同
-
window.name 利用在一個(gè)瀏覽器窗口內(nèi),載入所有的域名都是共享一個(gè)window.name
-
CORS CORS(Cross-origin resource sharing)跨域資源共享 服務(wù)器設(shè)置對(duì)CORS的支持原理:服務(wù)器設(shè)置Access-Control-Allow-Origin HTTP響應(yīng)頭之后,瀏覽器將會(huì)允許跨域請(qǐng)求
-
proxy代理 目前常用方式,通過服務(wù)器設(shè)置代理
-
window.postMessage() 利用h5新特性window.postMessage()
跨域傳送門 ? # 跨域,不可不知的基礎(chǔ)概念
Cookie、sessionStorage、localStorage 的區(qū)別
相同點(diǎn):
不同點(diǎn):
- cookie數(shù)據(jù)大小不能超過4k;sessionStorage和localStorage的存儲(chǔ)比cookie大得多,可以達(dá)到5M+
- cookie設(shè)置的過期時(shí)間之前一直有效;localStorage永久存儲(chǔ),瀏覽器關(guān)閉后數(shù)據(jù)不丟失除非主動(dòng)刪除數(shù)據(jù);sessionStorage數(shù)據(jù)在當(dāng)前瀏覽器窗口關(guān)閉后自動(dòng)刪除
- cookie的數(shù)據(jù)會(huì)自動(dòng)的傳遞到服務(wù)器;sessionStorage和localStorage數(shù)據(jù)保存在本地
粘包問題分析與對(duì)策
TCP粘包是指發(fā)送方發(fā)送的若干包數(shù)據(jù)到接收方接收時(shí)粘成一包,從接收緩沖區(qū)看,后一包數(shù)據(jù)的頭緊接著前一包數(shù)據(jù)的尾。
粘包出現(xiàn)原因
簡單得說,在流傳輸中出現(xiàn),UDP不會(huì)出現(xiàn)粘包,因?yàn)樗?strong>消息邊界
粘包情況有兩種,一種是粘在一起的包都是完整的數(shù)據(jù)包 ,另一種情況是粘在一起的包有不完整的包 。
為了避免粘包現(xiàn)象,可采取以下幾種措施:
(1)對(duì)于發(fā)送方引起的粘包現(xiàn)象,用戶可通過編程設(shè)置來避免,TCP提供了強(qiáng)制數(shù)據(jù)立即傳送的操作指令push ,TCP軟件收到該操作指令后,就立即將本段數(shù)據(jù)發(fā)送出去,而不必等待發(fā)送緩沖區(qū)滿;
(2)對(duì)于接收方引起的粘包,則可通過優(yōu)化程序設(shè)計(jì)、精簡接收進(jìn)程工作量、提高接收進(jìn)程優(yōu)先級(jí)等措施 ,使其及時(shí)接收數(shù)據(jù),從而盡量避免出現(xiàn)粘包現(xiàn)象;
(3)由接收方控制,將一包數(shù)據(jù)按結(jié)構(gòu)字段,人為控制分多次接收,然后合并,通過這種手段來避免粘包。分包多發(fā) 。
以上提到的三種措施,都有其不足之處。
(1)第一種編程設(shè)置方法雖然可以避免發(fā)送方引起的粘包,但它關(guān)閉了優(yōu)化算法,降低了網(wǎng)絡(luò)發(fā)送效率,影響應(yīng)用程序的性能,一般不建議使用。
(2)第二種方法只能減少出現(xiàn)粘包的可能性,但并不能完全避免粘包,當(dāng)發(fā)送頻率較高時(shí),或由于網(wǎng)絡(luò)突發(fā)可能使某個(gè)時(shí)間段數(shù)據(jù)包到達(dá)接收方較快,接收方還是有可能來不及接收,從而導(dǎo)致粘包。
(3)第三種方法雖然避免了粘包,但應(yīng)用程序的效率較低,對(duì)實(shí)時(shí)應(yīng)用的場(chǎng)合不適合。
一種比較周全的對(duì)策是:接收方創(chuàng)建一預(yù)處理線程,對(duì)接收到的數(shù)據(jù)包進(jìn)行預(yù)處理,將粘連的包分開。實(shí)驗(yàn)證明這種方法是高效可行的。
瀏覽器
從輸入U(xiǎn)RL到頁面加載的全過程
-
首先在瀏覽器中輸入U(xiǎn)RL
-
查找緩存:瀏覽器先查看瀏覽器緩存-系統(tǒng)緩存-路由緩存中是否有該地址頁面,如果有則顯示頁面內(nèi)容。如果沒有則進(jìn)行下一步。
- 瀏覽器緩存:瀏覽器會(huì)記錄DNS一段時(shí)間,因此,只是第一個(gè)地方解析DNS請(qǐng)求;
- 操作系統(tǒng)緩存:如果在瀏覽器緩存中不包含這個(gè)記錄,則會(huì)使系統(tǒng)調(diào)用操作系統(tǒng), 獲取操作系統(tǒng)的記錄(保存最近的DNS查詢緩存);
- 路由器緩存:如果上述兩個(gè)步驟均不能成功獲取DNS記錄,繼續(xù)搜索路由器緩存;
- ISP緩存:若上述均失敗,繼續(xù)向ISP搜索。
-
DNS域名解析:瀏覽器向DNS服務(wù)器發(fā)起請(qǐng)求,解析該URL中的域名對(duì)應(yīng)的IP地址。DNS服務(wù)器是基于UDP的,因此會(huì)用到UDP協(xié)議 。
-
建立TCP連接:解析出IP地址后,根據(jù)IP地址和默認(rèn)80端口,和服務(wù)器建立TCP連接
-
發(fā)起HTTP請(qǐng)求:瀏覽器發(fā)起讀取文件的HTTP請(qǐng)求,,該請(qǐng)求報(bào)文作為TCP三次握手的第三次數(shù)據(jù)發(fā)送給服務(wù)器
-
服務(wù)器響應(yīng)請(qǐng)求并返回結(jié)果:服務(wù)器對(duì)瀏覽器請(qǐng)求做出響應(yīng),并把對(duì)應(yīng)的html文件發(fā)送給瀏覽器
-
關(guān)閉TCP連接:通過四次揮手釋放TCP連接
-
瀏覽器渲染:客戶端(瀏覽器)解析HTML內(nèi)容并渲染出來,瀏覽器接收到數(shù)據(jù)包后的解析流程為:
- 構(gòu)建DOM樹:詞法分析然后解析成DOM樹(dom tree),是由dom元素及屬性節(jié)點(diǎn)組成,樹的根是document對(duì)象
- 構(gòu)建CSS規(guī)則樹:生成CSS規(guī)則樹(CSS Rule Tree)
- 構(gòu)建render樹:Web瀏覽器將DOM和CSSOM結(jié)合,并構(gòu)建出渲染樹(render tree)
- 布局(Layout):計(jì)算出每個(gè)節(jié)點(diǎn)在屏幕中的位置
- 繪制(Painting):即遍歷render樹,并使用UI后端層繪制每個(gè)節(jié)點(diǎn)。
-
JS引擎解析過程:調(diào)用JS引擎執(zhí)行JS代碼(JS的解釋階段,預(yù)處理階段,執(zhí)行階段生成執(zhí)行上下文,VO,作用域鏈、回收機(jī)制等等)
- 創(chuàng)建window對(duì)象:window對(duì)象也叫全局執(zhí)行環(huán)境,當(dāng)頁面產(chǎn)生時(shí)就被創(chuàng)建,所有的全局變量和函數(shù)都屬于window的屬性和方法,而DOM Tree也會(huì)映射在window的doucment對(duì)象上。當(dāng)關(guān)閉網(wǎng)頁或者關(guān)閉瀏覽器時(shí),全局執(zhí)行環(huán)境會(huì)被銷毀。
- 加載文件:完成js引擎分析它的語法與詞法是否合法,如果合法進(jìn)入預(yù)編譯
- 預(yù)編譯:在預(yù)編譯的過程中,瀏覽器會(huì)尋找全局變量聲明,把它作為window的屬性加入到window對(duì)象中,并給變量賦值為'undefined';尋找全局函數(shù)聲明,把它作為window的方法加入到window對(duì)象中,并將函數(shù)體賦值給他(匿名函數(shù)是不參與預(yù)編譯的,因?yàn)樗亲兞浚6兞刻嵘鳛椴缓侠淼牡胤皆贓S6中已經(jīng)解決了,函數(shù)提升還存在。
- 解釋執(zhí)行:執(zhí)行到變量就賦值,如果變量沒有被定義,也就沒有被預(yù)編譯直接賦值,在ES5非嚴(yán)格模式下這個(gè)變量會(huì)成為window的一個(gè)屬性,也就是成為全局變量。string、int這樣的值就是直接把值放在變量的存儲(chǔ)空間里,object對(duì)象就是把指針指向變量的存儲(chǔ)空間。函數(shù)執(zhí)行,就將函數(shù)的環(huán)境推入一個(gè)環(huán)境的棧中,執(zhí)行完成后再彈出,控制權(quán)交還給之前的環(huán)境。JS作用域其實(shí)就是這樣的執(zhí)行流機(jī)制實(shí)現(xiàn)的。
傳送門 ? # DNS域名解析過程 ?# 瀏覽器的工作原理
瀏覽器重繪與重排的區(qū)別?
重排/回流(Reflow) :當(dāng)DOM 的變化影響了元素的幾何信息,瀏覽器需要重新計(jì)算元素的幾何屬性,將其安放在界面中的正確位置,這個(gè)過程叫做重排。表現(xiàn)為重新生成布局,重新排列元素。
重繪(Repaint) : 當(dāng)一個(gè)元素的外觀發(fā)生改變,但沒有改變布局,重新把元素外觀繪制出來的過程,叫做重繪。表現(xiàn)為某些元素的外觀被改變
單單改變?cè)氐耐庥^,肯定不會(huì)引起網(wǎng)頁重新生成布局,但當(dāng)瀏覽器完成重排之后,將會(huì)重新繪制受到此次重排影響的部分
重排和重繪代價(jià)是高昂的,它們會(huì)破壞用戶體驗(yàn),并且讓UI展示非常遲緩,而相比之下重排的性能影響更大,在兩者無法避免的情況下,一般我們寧可選擇代價(jià)更小的重繪。
『重繪』不一定會(huì)出現(xiàn)『重排』,『重排』必然會(huì)出現(xiàn)『重繪』。
如何觸發(fā)重排和重繪?
任何改變用來構(gòu)建渲染樹的信息都會(huì)導(dǎo)致一次重排或重繪:
- 添加、刪除、更新DOM節(jié)點(diǎn)
- 通過display: none隱藏一個(gè)DOM節(jié)點(diǎn)-觸發(fā)重排和重繪
- 通過visibility: hidden隱藏一個(gè)DOM節(jié)點(diǎn)-只觸發(fā)重繪,因?yàn)闆]有幾何變化
- 移動(dòng)或者給頁面中的DOM節(jié)點(diǎn)添加動(dòng)畫
- 添加一個(gè)樣式表,調(diào)整樣式屬性
- 用戶行為,例如調(diào)整窗口大小,改變字號(hào),或者滾動(dòng)。
如何避免重繪或者重排?
-
集中改變樣式 ,不要一條一條地修改 DOM 的樣式。
-
不要把 DOM 結(jié)點(diǎn)的屬性值放在循環(huán)里當(dāng)成循環(huán)里的變量。
-
為動(dòng)畫的 HTML 元件使用 fixed 或 absoult 的 position ,那么修改他們的 CSS 是不會(huì) reflow 的。
-
不使用 table 布局。因?yàn)榭赡芎苄〉囊粋€(gè)小改動(dòng)會(huì)造成整個(gè) table 的重新布局。
-
盡量只修改position:absolute 或fixed 元素,對(duì)其他元素影響不大
-
動(dòng)畫開始GPU 加速,translate 使用3D 變化
-
提升為合成層
將元素提升為合成層有以下優(yōu)點(diǎn):
- 合成層的位圖,會(huì)交由 GPU 合成,比 CPU 處理要快
- 當(dāng)需要 repaint 時(shí),只需要 repaint 本身,不會(huì)影響到其他的層
- 對(duì)于 transform 和 opacity 效果,不會(huì)觸發(fā) layout 和 paint
提升合成層的最好方式是使用 CSS 的 will-change 屬性:
#target {
will-change: transform;
}
復(fù)制代碼
關(guān)于合成層的詳解請(qǐng)移步無線性能優(yōu)化:Composite
介紹下304過程
- a. 瀏覽器請(qǐng)求資源時(shí)首先命中資源的Expires 和 Cache-Control,Expires 受限于本地時(shí)間,如果修改了本地時(shí)間,可能會(huì)造成緩存失效,可以通過Cache-control: max-age指定最大生命周期,狀態(tài)仍然返回200,但不會(huì)請(qǐng)求數(shù)據(jù),在瀏覽器中能明顯看到from cache字樣。
- b. 強(qiáng)緩存失效,進(jìn)入?yún)f(xié)商緩存階段,首先驗(yàn)證ETagETag可以保證每一個(gè)資源是唯一的,資源變化都會(huì)導(dǎo)致ETag變化。服務(wù)器根據(jù)客戶端上送的If-None-Match值來判斷是否命中緩存。
- c. 協(xié)商緩存Last-Modify/If-Modify-Since階段,客戶端第一次請(qǐng)求資源時(shí),服務(wù)服返回的header中會(huì)加上Last-Modify,Last-modify是一個(gè)時(shí)間標(biāo)識(shí)該資源的最后修改時(shí)間。再次請(qǐng)求該資源時(shí),request的請(qǐng)求頭中會(huì)包含If-Modify-Since,該值為緩存之前返回的Last-Modify。服務(wù)器收到If-Modify-Since后,根據(jù)資源的最后修改時(shí)間判斷是否命中緩存。
瀏覽器的緩存機(jī)制 強(qiáng)制緩存 && 協(xié)商緩存
瀏覽器與服務(wù)器通信的方式為應(yīng)答模式,即是:瀏覽器發(fā)起HTTP請(qǐng)求 – 服務(wù)器響應(yīng)該請(qǐng)求。那么瀏覽器第一次向服務(wù)器發(fā)起該請(qǐng)求后拿到請(qǐng)求結(jié)果,會(huì)根據(jù)響應(yīng)報(bào)文中HTTP頭的緩存標(biāo)識(shí),決定是否緩存結(jié)果,是則將請(qǐng)求結(jié)果和緩存標(biāo)識(shí)存入瀏覽器緩存中,簡單的過程如下圖:
由上圖我們可以知道:
- 瀏覽器每次發(fā)起請(qǐng)求,都會(huì)
先在瀏覽器緩存中查找該請(qǐng)求的結(jié)果以及緩存標(biāo)識(shí)
- 瀏覽器每次拿到返回的請(qǐng)求結(jié)果都會(huì)
將該結(jié)果和緩存標(biāo)識(shí)存入瀏覽器緩存中
以上兩點(diǎn)結(jié)論就是瀏覽器緩存機(jī)制的關(guān)鍵,他確保了每個(gè)請(qǐng)求的緩存存入與讀取,只要我們?cè)倮斫鉃g覽器緩存的使用規(guī)則,那么所有的問題就迎刃而解了。為了方便理解,這里根據(jù)是否需要向服務(wù)器重新發(fā)起HTTP請(qǐng)求將緩存過程分為兩個(gè)部分,分別是強(qiáng)制緩存 和協(xié)商緩存 。
-
強(qiáng)制緩存
強(qiáng)制緩存就是向?yàn)g覽器緩存查找該請(qǐng)求結(jié)果,并根據(jù)該結(jié)果的緩存規(guī)則來決定是否使用該緩存結(jié)果的過程。 當(dāng)瀏覽器向服務(wù)器發(fā)起請(qǐng)求時(shí),服務(wù)器會(huì)將緩存規(guī)則放入HTTP響應(yīng)報(bào)文的HTTP頭中和請(qǐng)求結(jié)果一起返回給瀏覽器,控制強(qiáng)制緩存的字段分別是 Expires 和 Cache-Control ,其中Cache-Control優(yōu)先級(jí)比Expires高。
強(qiáng)制緩存的情況主要有三種(暫不分析協(xié)商緩存過程),如下:
- 不存在該緩存結(jié)果和緩存標(biāo)識(shí),強(qiáng)制緩存失效,則直接向服務(wù)器發(fā)起請(qǐng)求(跟第一次發(fā)起請(qǐng)求一致)。
- 存在該緩存結(jié)果和緩存標(biāo)識(shí),但該結(jié)果已失效,強(qiáng)制緩存失效,則使用協(xié)商緩存。
- 存在該緩存結(jié)果和緩存標(biāo)識(shí),且該結(jié)果尚未失效,強(qiáng)制緩存生效,直接返回該結(jié)果
-
協(xié)商緩存
協(xié)商緩存就是強(qiáng)制緩存失效后,瀏覽器攜帶緩存標(biāo)識(shí)向服務(wù)器發(fā)起請(qǐng)求,由服務(wù)器根據(jù)緩存標(biāo)識(shí)決定是否使用緩存的過程 ,同樣,協(xié)商緩存的標(biāo)識(shí)也是在響應(yīng)報(bào)文的HTTP頭中和請(qǐng)求結(jié)果一起返回給瀏覽器的,控制協(xié)商緩存的字段分別有:Last-Modified / If-Modified-Since 和 Etag / If-None-Match ,其中Etag / If-None-Match的優(yōu)先級(jí)比Last-Modified / If-Modified-Since高。協(xié)商緩存主要有以下兩種情況:
- 協(xié)商緩存生效,返回304
- 協(xié)商緩存失效,返回200和請(qǐng)求結(jié)果結(jié)果
傳送門 ? # 徹底理解瀏覽器的緩存機(jī)制
說下進(jìn)程、線程和協(xié)程
進(jìn)程是一個(gè)具有一定獨(dú)立功能的程序在一個(gè)數(shù)據(jù)集上的一次動(dòng)態(tài)執(zhí)行的過程,是操作系統(tǒng)進(jìn)行資源分配和調(diào)度的一個(gè)獨(dú)立單位 ,是應(yīng)用程序運(yùn)行的載體。進(jìn)程是一種抽象的概念,從來沒有統(tǒng)一的標(biāo)準(zhǔn)定義。
線程是程序執(zhí)行中一個(gè)單一的順序控制流程,是程序執(zhí)行流的最小單元 ,是處理器調(diào)度和分派的基本單位。一個(gè)進(jìn)程可以有一個(gè)或多個(gè)線程,各個(gè)線程之間共享程序的內(nèi)存空間(也就是所在進(jìn)程的內(nèi)存空間)。一個(gè)標(biāo)準(zhǔn)的線程由線程ID、當(dāng)前指令指針(PC)、寄存器和堆棧組成。而進(jìn)程由內(nèi)存空間(代碼、數(shù)據(jù)、進(jìn)程空間、打開的文件)和一個(gè)或多個(gè)線程組成。
協(xié)程,英文Coroutines,是一種基于線程之上,但又比線程更加輕量級(jí)的存在 ,這種由程序員自己寫程序來管理的輕量級(jí)線程叫做『用戶空間線程』,具有對(duì)內(nèi)核來說不可見的特性。
進(jìn)程和線程的區(qū)別與聯(lián)系
【區(qū)別】:
調(diào)度:線程作為調(diào)度和分配的基本單位,進(jìn)程作為擁有資源的基本單位;
并發(fā)性:不僅進(jìn)程之間可以并發(fā)執(zhí)行,同一個(gè)進(jìn)程的多個(gè)線程之間也可并發(fā)執(zhí)行;
擁有資源:進(jìn)程是擁有資源的一個(gè)獨(dú)立單位,線程不擁有系統(tǒng)資源,但可以訪問隸屬于進(jìn)程的資源。
系統(tǒng)開銷:在創(chuàng)建或撤消進(jìn)程時(shí),由于系統(tǒng)都要為之分配和回收資源,導(dǎo)致系統(tǒng)的開銷明顯大于創(chuàng)建或撤消線程時(shí)的開銷。但是進(jìn)程有獨(dú)立的地址空間,一個(gè)進(jìn)程崩潰后,在保護(hù)模式下不會(huì)對(duì)其它進(jìn)程產(chǎn)生影響,而線程只是一個(gè)進(jìn)程中的不同執(zhí)行路徑。線程有自己的堆棧和局部變量,但線程之間沒有單獨(dú)的地址空間,一個(gè)進(jìn)程死掉就等于所有的線程死掉,所以多進(jìn)程的程序要比多線程的程序健壯,但在進(jìn)程切換時(shí),耗費(fèi)資源較大,效率要差一些。
【聯(lián)系】:
一個(gè)線程只能屬于一個(gè)進(jìn)程,而一個(gè)進(jìn)程可以有多個(gè)線程,但至少有一個(gè)線程;
資源分配給進(jìn)程,同一進(jìn)程的所有線程共享該進(jìn)程的所有資源;
處理機(jī)分給線程,即真正在處理機(jī)上運(yùn)行的是線程;
線程在執(zhí)行過程中,需要協(xié)作同步。不同進(jìn)程的線程間要利用消息通信的辦法實(shí)現(xiàn)同步。
傳送門 ? # 一文搞懂進(jìn)程、線程、協(xié)程及JS協(xié)程的發(fā)展
?了解更多
關(guān)于瀏覽器傳送門 ?# 深入了解現(xiàn)代 Web 瀏覽器
HTML && CSS
HTML5 新特性、語義化
-
概念:
HTML5的語義化指的是合理正確的使用語義化的標(biāo)簽來創(chuàng)建頁面結(jié)構(gòu) ?!菊_的標(biāo)簽做正確的事】
-
語義化標(biāo)簽:
header nav main article section aside footer
-
語義化的優(yōu)點(diǎn):
- 在
沒CSS樣式的情況下,頁面整體也會(huì)呈現(xiàn)很好的結(jié)構(gòu)效果
代碼結(jié)構(gòu)清晰 ,易于閱讀,
利于開發(fā)和維護(hù) 方便其他設(shè)備解析(如屏幕閱讀器)根據(jù)語義渲染網(wǎng)頁。
有利于搜索引擎優(yōu)化(SEO) ,搜索引擎爬蟲會(huì)根據(jù)不同的標(biāo)簽來賦予不同的權(quán)重
CSS 選擇器及優(yōu)先級(jí)
選擇器
- id選擇器(#myid)
- 類選擇器(.myclass)
- 屬性選擇器(a[rel='external'])
- 偽類選擇器(a:hover, li:nth-child)
- 標(biāo)簽選擇器(div, h1,p)
- 相鄰選擇器(h1 + p)
- 子選擇器(ul > li)
- 后代選擇器(li a)
- 通配符選擇器(*)
優(yōu)先級(jí):
!important
- 內(nèi)聯(lián)樣式(1000)
- ID選擇器(0100)
- 類選擇器/屬性選擇器/偽類選擇器(0010)
- 元素選擇器/偽元素選擇器(0001)
- 關(guān)系選擇器/通配符選擇器(0000)
帶!important 標(biāo)記的樣式屬性優(yōu)先級(jí)最高; 樣式表的來源相同時(shí):!important > 行內(nèi)樣式>ID選擇器 > 類選擇器 > 標(biāo)簽 > 通配符 > 繼承 > 瀏覽器默認(rèn)屬性
position 屬性的值有哪些及其區(qū)別
固定定位 fixed: 元素的位置相對(duì)于瀏覽器窗口是固定位置,即使窗口是滾動(dòng)的它也不會(huì)移動(dòng)。Fixed 定 位使元素的位置與文檔流無關(guān),因此不占據(jù)空間。 Fixed 定位的元素和其他元素重疊。
相對(duì)定位 relative: 如果對(duì)一個(gè)元素進(jìn)行相對(duì)定位,它將出現(xiàn)在它所在的位置上。然后,可以通過設(shè)置垂直 或水平位置,讓這個(gè)元素“相對(duì)于”它的起點(diǎn)進(jìn)行移動(dòng)。 在使用相對(duì)定位時(shí),無論是 否進(jìn)行移動(dòng),元素仍然占據(jù)原來的空間。因此,移動(dòng)元素會(huì)導(dǎo)致它覆蓋其它框。
絕對(duì)定位 absolute: 絕對(duì)定位的元素的位置相對(duì)于最近的已定位父元素,如果元素沒有已定位的父元素,那 么它的位置相對(duì)于。absolute 定位使元素的位置與文檔流無關(guān),因此不占據(jù)空間。 absolute 定位的元素和其他元素重疊。
粘性定位 sticky: 元素先按照普通文檔流定位,然后相對(duì)于該元素在流中的 flow root(BFC)和 containing block(最近的塊級(jí)祖先元素)定位。而后,元素定位表現(xiàn)為在跨越特定閾值前為相對(duì)定 位,之后為固定定位。
默認(rèn)定位 Static: 默認(rèn)值。沒有定位,元素出現(xiàn)在正常的流中(忽略 top, bottom, left, right 或者 z-index 聲 明)。 inherit: 規(guī)定應(yīng)該從父元素繼承 position 屬性的值。
box-sizing屬性
box-sizing 規(guī)定兩個(gè)并排的帶邊框的框,語法為 box-sizing:content-box/border-box/inherit
content-box:寬度和高度分別應(yīng)用到元素的內(nèi)容框,在寬度和高度之外繪制元素的內(nèi)邊距和邊框?!緲?biāo)準(zhǔn)盒子模型】
border-box:為元素設(shè)定的寬度和高度決定了元素的邊框盒?!綢E 盒子模型】
inherit:繼承父元素的 box-sizing 值。
CSS 盒子模型
CSS 盒模型本質(zhì)上是一個(gè)盒子,它包括:邊距,邊框,填充和實(shí)際內(nèi)容。CSS 中的盒子模型包括 IE 盒子模型和標(biāo)準(zhǔn)的 W3C 盒子模型。
在標(biāo)準(zhǔn)的盒子模型中,width 指 content 部分的寬度 。
在 IE 盒子模型中,width 表示 content+padding+border 這三個(gè)部分的寬度 。
故在計(jì)算盒子的寬度時(shí)存在差異:
標(biāo)準(zhǔn)盒模型: 一個(gè)塊的總寬度 = width+margin(左右)+padding(左右)+border(左右)
怪異盒模型: 一個(gè)塊的總寬度 = width+margin(左右)(既 width 已經(jīng)包含了 padding 和 border 值)
BFC(塊級(jí)格式上下文)
BFC的概念
BFC 是 Block Formatting Context 的縮寫,即塊級(jí)格式化上下文。BFC 是CSS布局的一個(gè)概念,是一個(gè)獨(dú)立的渲染區(qū)域,規(guī)定了內(nèi)部box如何布局, 并且這個(gè)區(qū)域的子元素不會(huì)影響到外面的元素,其中比較重要的布局規(guī)則有內(nèi)部 box 垂直放置,計(jì)算 BFC 的高度的時(shí)候,浮動(dòng)元素也參與計(jì)算。
BFC的原理布局規(guī)則
- 內(nèi)部的Box會(huì)在
垂直方向 ,一個(gè)接一個(gè)地放置
- Box
垂直方向的距離由margin決定 。屬于同一個(gè)BFC的兩個(gè)相鄰Box的margin會(huì)發(fā)生重疊
- 每個(gè)元素的margin box的左邊, 與包含塊border box的左邊相接觸(對(duì)于從左往右的格式化,否則相反
- BFC的區(qū)域
不會(huì)與float box重疊
- BFC是一個(gè)獨(dú)立容器,容器里面的
子元素不會(huì)影響到外面的元素
- 計(jì)算BFC的高度時(shí),
浮動(dòng)元素也參與計(jì)算高度
- 元素的類型和
display屬性,決定了這個(gè)Box的類型 。不同類型的Box會(huì)參與不同的Formatting Context 。
如何創(chuàng)建BFC?
- 根元素,即HTML元素
- float的值不為none
- position為absolute或fixed
- display的值為inline-block、table-cell、table-caption
- overflow的值不為visible
BFC的使用場(chǎng)景
- 去除邊距重疊現(xiàn)象
- 清除浮動(dòng)(讓父元素的高度包含子浮動(dòng)元素)
- 避免某元素被浮動(dòng)元素覆蓋
- 避免多列布局由于寬度計(jì)算四舍五入而自動(dòng)換行
讓一個(gè)元素水平垂直居中
-
水平居中
-
對(duì)于 行內(nèi)元素 : text-align: center ;
-
對(duì)于確定寬度的塊級(jí)元素:
(1)width和margin實(shí)現(xiàn)。margin: 0 auto ;
(2)絕對(duì)定位和margin-left: (父width - 子width)/2, 前提是父元素position: relative
-
對(duì)于寬度未知的塊級(jí)元素
(1)table標(biāo)簽配合margin左右auto實(shí)現(xiàn)水平居中 。使用table標(biāo)簽(或直接將塊級(jí)元素設(shè)值為 display:table),再通過給該標(biāo)簽添加左右margin為auto。
(2)inline-block實(shí)現(xiàn)水平居中方法。display:inline-block和text-align:center實(shí)現(xiàn)水平居中。
(3)絕對(duì)定位+transform ,translateX可以移動(dòng)本身元素的50%。
(4)flex布局使用justify-content:center
-
垂直居中
- 利用
line-height 實(shí)現(xiàn)居中,這種方法適合純文字類
- 通過設(shè)置父容器 相對(duì)定位 ,子級(jí)設(shè)置
絕對(duì)定位 ,標(biāo)簽通過margin實(shí)現(xiàn)自適應(yīng)居中
- 彈性布局 flex :父級(jí)設(shè)置display: flex; 子級(jí)設(shè)置margin為auto實(shí)現(xiàn)自適應(yīng)居中
- 父級(jí)設(shè)置相對(duì)定位,子級(jí)設(shè)置絕對(duì)定位,并且通過位移 transform 實(shí)現(xiàn)
table 布局 ,父級(jí)通過轉(zhuǎn)換成表格形式,然后子級(jí)設(shè)置 vertical-align 實(shí)現(xiàn) 。(需要注意的是:vertical-align: middle使用的前提條件是內(nèi)聯(lián)元素以及display值為table-cell的元素)。
傳送門 ? # 圖解CSS水平垂直居中常見面試方法
隱藏頁面中某個(gè)元素的方法
1.opacity:0 ,該元素隱藏起來了,但不會(huì)改變頁面布局,并且,如果該元素已經(jīng)綁定 一些事件,如click 事件,那么點(diǎn)擊該區(qū)域,也能觸發(fā)點(diǎn)擊事件的
2.visibility:hidden ,該元素隱藏起來了,但不會(huì)改變頁面布局,但是不會(huì)觸發(fā)該元素已 經(jīng)綁定的事件 ,隱藏對(duì)應(yīng)元素,在文檔布局中仍保留原來的空間(重繪)
3.display:none ,把元素隱藏起來,并且會(huì)改變頁面布局,可以理解成在頁面中把該元素。 不顯示對(duì)應(yīng)的元素,在文檔布局中不再分配空間(回流+重繪)
該問題會(huì)引出 回流和重繪
用CSS實(shí)現(xiàn)三角符號(hào)
/*記憶口訣:盒子寬高均為零,三面邊框皆透明。 */
div:after{
position: absolute;
width: 0px;
height: 0px;
content: ' ';
border-right: 100px solid transparent;
border-top: 100px solid #ff0;
border-left: 100px solid transparent;
border-bottom: 100px solid transparent;
}
復(fù)制代碼
頁面布局
1.Flex 布局
布局的傳統(tǒng)解決方案,基于盒狀模型,依賴 display 屬性 + position 屬性 + float 屬性。它對(duì)于那些特殊布局非常不方便,比如,垂直居中就不容易實(shí)現(xiàn)。
Flex 是 Flexible Box 的縮寫,意為'彈性布局',用來為盒狀模型提供最大的靈活性。指定容器 display: flex 即可。 簡單的分為容器屬性和元素屬性。
容器的屬性:
- flex-direction:決定主軸的方向(即子 item 的排列方法)flex-direction: row | row-reverse | column | column-reverse;
- flex-wrap:決定換行規(guī)則 flex-wrap: nowrap | wrap | wrap-reverse;
- flex-flow: .box { flex-flow: || ; }
- justify-content:對(duì)其方式,水平主軸對(duì)齊方式
- align-items:對(duì)齊方式,豎直軸線方向
- align-content
項(xiàng)目的屬性(元素的屬性):
- order 屬性:定義項(xiàng)目的排列順序,順序越小,排列越靠前,默認(rèn)為 0
- flex-grow 屬性:定義項(xiàng)目的放大比例,即使存在空間,也不會(huì)放大
- flex-shrink 屬性:定義了項(xiàng)目的縮小比例,當(dāng)空間不足的情況下會(huì)等比例的縮小,如果 定義個(gè) item 的 flow-shrink 為 0,則為不縮小
- flex-basis 屬性:定義了在分配多余的空間,項(xiàng)目占據(jù)的空間。
- flex:是 flex-grow 和 flex-shrink、flex-basis 的簡寫,默認(rèn)值為 0 1 auto。
- align-self:允許單個(gè)項(xiàng)目與其他項(xiàng)目不一樣的對(duì)齊方式,可以覆蓋
- align-items,默認(rèn)屬 性為 auto,表示繼承父元素的 align-items 比如說,用 flex 實(shí)現(xiàn)圣杯布局
2.Rem 布局
首先 Rem 相對(duì)于根(html)的 font-size 大小來計(jì)算。簡單的說它就是一個(gè)相對(duì)單例 如:font-size:10px;,那么(1rem = 10px)了解計(jì)算原理后首先解決怎么在不同設(shè)備上設(shè)置 html 的 font-size 大小。其實(shí) rem 布局的本質(zhì)是等比縮放,一般是基于寬度。
優(yōu)點(diǎn):可以快速適用移動(dòng)端布局,字體,圖片高度
缺點(diǎn):
①目前 ie 不支持,對(duì) pc 頁面來講使用次數(shù)不多;
②數(shù)據(jù)量大:所有的圖片,盒子都需要我們?nèi)ソo一個(gè)準(zhǔn)確的值;才能保證不同機(jī)型的適配;
③在響應(yīng)式布局中,必須通過 js 來動(dòng)態(tài)控制根元素 font-size 的大小。也就是說 css 樣式和 js 代碼有一定的耦合性。且必須將改變 font-size 的代碼放在 css 樣式之前。
3.百分比布局
通過百分比單位 ' % ' 來實(shí)現(xiàn)響應(yīng)式的效果。通過百分比單位可以使得瀏覽器中的組件的寬和高隨著瀏覽器的變化而變化,從而實(shí)現(xiàn)響應(yīng)式的效果。 直觀的理解,我們可能會(huì)認(rèn)為子元素的百分比完全相對(duì)于直接父元素,height 百分比相 對(duì)于 height,width 百分比相對(duì)于 width。 padding、border、margin 等等不論是垂直方向還是水平方向,都相對(duì)于直接父元素的 width。 除了 border-radius 外,還有比如 translate、background-size 等都是相對(duì)于自身的。
缺點(diǎn):
(1)計(jì)算困難
(2)各個(gè)屬性中如果使用百分比,相對(duì)父元素的屬性并不是唯一的。造成我們使用百分比單位容易使布局問題變得復(fù)雜。
4.浮動(dòng)布局
浮動(dòng)布局:當(dāng)元素浮動(dòng)以后可以向左或向右移動(dòng),直到它的外邊緣碰到包含它的框或者另外一個(gè)浮動(dòng)元素的邊框?yàn)橹?。元素浮?dòng)以后會(huì)脫離正常的文檔流,所以文檔的普通流中的框就變的好像浮動(dòng)元素不存在一樣。
優(yōu)點(diǎn)
這樣做的優(yōu)點(diǎn)就是在圖文混排的時(shí)候可以很好的使文字環(huán)繞在圖片周圍。另外當(dāng)元素浮動(dòng)了起來之后,它有著塊級(jí)元素的一些性質(zhì)例如可以設(shè)置寬高等,但它與inline-block還是有一些區(qū)別的,第一個(gè)就是關(guān)于橫向排序的時(shí)候,float可以設(shè)置方向而inline-block方向是固定的;還有一個(gè)就是inline-block在使用時(shí)有時(shí)會(huì)有空白間隙的問題
缺點(diǎn)
最明顯的缺點(diǎn)就是浮動(dòng)元素一旦脫離了文檔流,就無法撐起父元素,會(huì)造成父級(jí)元素高度塌陷 。
如何使用rem或viewport進(jìn)行移動(dòng)端適配
rem適配原理:
改變了一個(gè)元素在不同設(shè)備上占據(jù)的css像素的個(gè)數(shù)
rem適配的優(yōu)缺點(diǎn)
- 優(yōu)點(diǎn):沒有破壞完美視口
- 缺點(diǎn):px值轉(zhuǎn)換rem太過于復(fù)雜(下面我們使用less來解決這個(gè)問題)
viewport適配的原理
viewport適配方案中,每一個(gè)元素在不同設(shè)備上占據(jù)的css像素的個(gè)數(shù)是一樣的。但是css像素和物理像素的比例是不一樣的,等比的
viewport適配的優(yōu)缺點(diǎn)
- 在我們?cè)O(shè)計(jì)圖上所量取的大小即為我們可以設(shè)置的像素大小,即所量即所設(shè)
- 缺點(diǎn)破壞完美視口
清除浮動(dòng)的方式
<div class='parent'>
//添加額外標(biāo)簽并且添加clear屬性
<div style='clear:both'></div>
//也可以加一個(gè)br標(biāo)簽
</div>
復(fù)制代碼
- 父級(jí)添加overflow屬性,或者設(shè)置高度
- 建立偽類選擇器清除浮動(dòng)
//在css中添加:after偽元素
.parent:after{
/* 設(shè)置添加子元素的內(nèi)容是空 */
content: '';
/* 設(shè)置添加子元素為塊級(jí)元素 */
display: block;
/* 設(shè)置添加的子元素的高度0 */
height: 0;
/* 設(shè)置添加子元素看不見 */
visibility: hidden;
/* 設(shè)置clear:both */
clear: both;
}
復(fù)制代碼
JS、TS、ES6
JS中的8種數(shù)據(jù)類型及區(qū)別
包括值類型(基本對(duì)象類型)和引用類型(復(fù)雜對(duì)象類型)
基本類型(值類型): Number(數(shù)字),String(字符串),Boolean(布爾),Symbol(符號(hào)),null(空),undefined(未定義)在內(nèi)存中占據(jù)固定大小,保存在棧內(nèi)存中
引用類型(復(fù)雜數(shù)據(jù)類型): Object(對(duì)象)、Function(函數(shù))。其他還有Array(數(shù)組)、Date(日期)、RegExp(正則表達(dá)式)、特殊的基本包裝類型(String、Number、Boolean) 以及單體內(nèi)置對(duì)象(Global、Math)等 引用類型的值是對(duì)象 保存在堆內(nèi)存中,棧內(nèi)存存儲(chǔ)的是對(duì)象的變量標(biāo)識(shí)符以及對(duì)象在堆內(nèi)存中的存儲(chǔ)地址。
傳送門 ?# JavaScript 數(shù)據(jù)類型之 Symbol、BigInt
JS中的數(shù)據(jù)類型檢測(cè)方案
1.typeof
console.log(typeof 1); // number
console.log(typeof true); // boolean
console.log(typeof 'mc'); // string
console.log(typeof Symbol) // function
console.log(typeof Symbol()); // symbol
console.log(typeof function(){}); // function
console.log(typeof console.log()); // undefined
console.log(typeof []); // object
console.log(typeof {}); // object
console.log(typeof null); // object
console.log(typeof undefined); // undefined
復(fù)制代碼
優(yōu)點(diǎn):能夠快速區(qū)分基本數(shù)據(jù)類型
缺點(diǎn):不能將Object、Array和Null區(qū)分,都返回object
2.instanceof
console.log(1 instanceof Number); // false
console.log(true instanceof Boolean); // false
console.log('str' instanceof String); // false
console.log([] instanceof Array); // true
console.log(function(){} instanceof Function); // true
console.log({} instanceof Object); // true
復(fù)制代碼
優(yōu)點(diǎn):能夠區(qū)分Array、Object和Function,適合用于判斷自定義的類實(shí)例對(duì)象
缺點(diǎn):Number,Boolean,String基本數(shù)據(jù)類型不能判斷
3.Object.prototype.toString.call()
var toString = Object.prototype.toString;
console.log(toString.call(1)); //[object Number]
console.log(toString.call(true)); //[object Boolean]
console.log(toString.call('mc')); //[object String]
console.log(toString.call([])); //[object Array]
console.log(toString.call({})); //[object Object]
console.log(toString.call(function(){})); //[object Function]
console.log(toString.call(undefined)); //[object Undefined]
console.log(toString.call(null)); //[object Null]
復(fù)制代碼
優(yōu)點(diǎn):精準(zhǔn)判斷數(shù)據(jù)類型
缺點(diǎn):寫法繁瑣不容易記,推薦進(jìn)行封裝后使用
var && let && const
ES6之前創(chuàng)建變量用的是var,之后創(chuàng)建變量用的是let/const
三者區(qū)別:
- var定義的變量,
沒有塊的概念,可以跨塊訪問 , 不能跨函數(shù)訪問。
let定義的變量,只能在塊作用域里訪問,不能跨塊訪問,也不能跨函數(shù)訪問。
const用來定義常量,使用時(shí)必須初始化(即必須賦值),只能在塊作用域里訪問,且不能修改。
- var可以
先使用,后聲明 ,因?yàn)榇嬖谧兞刻嵘?;let必須先聲明后使用。
- var是允許在相同作用域內(nèi)
重復(fù)聲明同一個(gè)變量 的,而let與const不允許這一現(xiàn)象。
- 在全局上下文中,基于let聲明的全局變量和全局對(duì)象GO(window)沒有任何關(guān)系 ;
var聲明的變量會(huì)和GO有映射關(guān)系;
會(huì)產(chǎn)生暫時(shí)性死區(qū) :
暫時(shí)性死區(qū)是瀏覽器的bug:檢測(cè)一個(gè)未被聲明的變量類型時(shí),不會(huì)報(bào)錯(cuò),會(huì)返回undefined
如:console.log(typeof a) //undefined
而:console.log(typeof a)//未聲明之前不能使用
let a
- let /const/function會(huì)把當(dāng)前所在的大括號(hào)(除函數(shù)之外)作為一個(gè)全新的塊級(jí)上下文,應(yīng)用這個(gè)機(jī)制,在開發(fā)項(xiàng)目的時(shí)候,遇到循環(huán)事件綁定等類似的需求,無需再自己構(gòu)建閉包來存儲(chǔ),只要基于let的塊作用特征即可解決
JS垃圾回收機(jī)制
-
項(xiàng)目中,如果存在大量不被釋放的內(nèi)存(堆/棧/上下文),頁面性能會(huì)變得很慢。當(dāng)某些代碼操作不能被合理釋放,就會(huì)造成內(nèi)存泄漏。我們盡可能減少使用閉包,因?yàn)樗鼤?huì)消耗內(nèi)存。
-
瀏覽器垃圾回收機(jī)制/內(nèi)存回收機(jī)制:
瀏覽器的Javascript 具有自動(dòng)垃圾回收機(jī)制(GC:Garbage Collecation ),垃圾收集器會(huì)定期(周期性)找出那些不在繼續(xù)使用的變量,然后釋放其內(nèi)存。
標(biāo)記清除:在js 中,最常用的垃圾回收機(jī)制是標(biāo)記清除:當(dāng)變量進(jìn)入執(zhí)行環(huán)境時(shí),被標(biāo)記為“進(jìn)入環(huán)境”,當(dāng)變量離開執(zhí)行環(huán)境時(shí),會(huì)被標(biāo)記為“離開環(huán)境”。垃圾回收器會(huì)銷毀那些帶標(biāo)記的值并回收它們所占用的內(nèi)存空間。
谷歌瀏覽器:“查找引用”,瀏覽器不定時(shí)去查找當(dāng)前內(nèi)存的引用,如果沒有被占用了,瀏覽器會(huì)回收它;如果被占用,就不能回收。
IE瀏覽器:“引用計(jì)數(shù)法”,當(dāng)前內(nèi)存被占用一次,計(jì)數(shù)累加1次,移除占用就減1,減到0時(shí),瀏覽器就回收它。
-
優(yōu)化手段:內(nèi)存優(yōu)化 ; 手動(dòng)釋放:取消內(nèi)存的占用即可。
(1)堆內(nèi)存:fn = null 【null:空指針對(duì)象】
(2)棧內(nèi)存:把上下文中,被外部占用的堆的占用取消即可。
-
內(nèi)存泄漏
在 JS 中,常見的內(nèi)存泄露主要有 4 種,全局變量、閉包、DOM 元素的引用、定時(shí)器
作用域和作用域鏈
創(chuàng)建函數(shù)的時(shí)候,已經(jīng)聲明了當(dāng)前函數(shù)的作用域==>當(dāng)前創(chuàng)建函數(shù)所處的上下文 。如果是在全局下創(chuàng)建的函數(shù)就是[[scope]]:EC(G) ,函數(shù)執(zhí)行的時(shí)候,形成一個(gè)全新的私有上下文EC(FN) ,供字符串代碼執(zhí)行(進(jìn)棧執(zhí)行)
定義:簡單來說作用域就是變量與函數(shù)的可訪問范圍,由當(dāng)前環(huán)境與上層環(huán)境的一系列變量對(duì)象組成
1.全局作用域:代碼在程序的任何地方都能被訪問,window 對(duì)象的內(nèi)置屬性都擁有全局作用域。
2.函數(shù)作用域:在固定的代碼片段才能被訪問
作用:作用域最大的用處就是隔離變量 ,不同作用域下同名變量不會(huì)有沖突。
作用域鏈參考鏈接一般情況下,變量到 創(chuàng)建該變量 的函數(shù)的作用域中取值。但是如果在當(dāng)前作用域中沒有查到,就會(huì)向上級(jí)作用域去查,直到查到全局作用域,這么一個(gè)查找過程形成的鏈條就叫做作用域鏈。
閉包的兩大作用:保存/保護(hù)
(1)保護(hù):劃分一個(gè)獨(dú)立的代碼執(zhí)行區(qū)域,在這個(gè)區(qū)域中有自己私有變量存儲(chǔ)的空間,保護(hù)自己的私有變量不受外界干擾(操作自己的私有變量和外界沒有關(guān)系);
(2)保存:如果當(dāng)前上下文不被釋放【只要上下文中的某個(gè)東西被外部占用即可】,則存儲(chǔ)的這些私有變量也不會(huì)被釋放,可以供其下級(jí)上下文中調(diào)取使用,相當(dāng)于把一些值保存起來了;
我們把函數(shù)執(zhí)行形成私有上下文,來保護(hù)和保存私有變量機(jī)制稱為閉包 。
閉包是指有權(quán)訪問另一個(gè)函數(shù)作用域中的變量的函數(shù)--《JavaScript高級(jí)程序設(shè)計(jì)》
稍全面的回答: 在js中變量的作用域?qū)儆诤瘮?shù)作用域, 在函數(shù)執(zhí)行完后,作用域就會(huì)被清理,內(nèi)存也會(huì)隨之被回收,但是由于閉包函數(shù)是建立在函數(shù)內(nèi)部的子函數(shù), 由于其可訪問上級(jí)作用域,即使上級(jí)函數(shù)執(zhí)行完, 作用域也不會(huì)隨之銷毀, 這時(shí)的子函數(shù)(也就是閉包),便擁有了訪問上級(jí)作用域中變量的權(quán)限,即使上級(jí)函數(shù)執(zhí)行完后作用域內(nèi)的值也不會(huì)被銷毀。
-
閉包的特性:
-
1、內(nèi)部函數(shù)可以訪問定義他們外部函數(shù)的參數(shù)和變量。(作用域鏈的向上查找,把外圍的作用域中的變量值存儲(chǔ)在內(nèi)存中而不是在函數(shù)調(diào)用完畢后銷毀)設(shè)計(jì)私有的方法和變量,避免全局變量的污染。
1.1.閉包是密閉的容器,,類似于set、map容器,存儲(chǔ)數(shù)據(jù)的
1.2.閉包是一個(gè)對(duì)象,存放數(shù)據(jù)的格式為 key-value 形式
-
2、函數(shù)嵌套函數(shù)
-
3、本質(zhì)是將函數(shù)內(nèi)部和外部連接起來。優(yōu)點(diǎn)是可以讀取函數(shù)內(nèi)部的變量,讓這些變量的值始終保存在內(nèi)存中,不會(huì)在函數(shù)被調(diào)用之后自動(dòng)清除
-
閉包形成的條件:
- 函數(shù)的嵌套
- 內(nèi)部函數(shù)引用外部函數(shù)的局部變量,延長外部函數(shù)的變量生命周期
-
閉包的用途:
- 模仿塊級(jí)作用域
- 保護(hù)外部函數(shù)的變量 能夠訪問函數(shù)定義時(shí)所在的詞法作用域(阻止其被回收)
- 封裝私有化變量
- 創(chuàng)建模塊
-
閉包應(yīng)用場(chǎng)景
閉包的兩個(gè)場(chǎng)景,閉包的兩大作用:保存/保護(hù) 。 在開發(fā)中, 其實(shí)我們隨處可見閉包的身影, 大部分前端JavaScript 代碼都是“事件驅(qū)動(dòng)”的,即一個(gè)事件綁定的回調(diào)方法; 發(fā)送ajax請(qǐng)求成功|失敗的回調(diào);setTimeout的延時(shí)回調(diào);或者一個(gè)函數(shù)內(nèi)部返回另一個(gè)匿名函數(shù),這些都是閉包的應(yīng)用。
-
閉包的優(yōu)點(diǎn):延長局部變量的生命周期
-
閉包缺點(diǎn):會(huì)導(dǎo)致函數(shù)的變量一直保存在內(nèi)存中,過多的閉包可能會(huì)導(dǎo)致內(nèi)存泄漏
JS 中 this 的五種情況
- 作為普通函數(shù)執(zhí)行時(shí),
this 指向window 。
- 當(dāng)函數(shù)作為對(duì)象的方法被調(diào)用時(shí),
this 就會(huì)指向該對(duì)象 。
- 構(gòu)造器調(diào)用,
this 指向返回的這個(gè)對(duì)象 。
- 箭頭函數(shù) 箭頭函數(shù)的
this 綁定看的是this所在函數(shù)定義在哪個(gè)對(duì)象下 ,就綁定哪個(gè)對(duì)象。如果有嵌套的情況,則this綁定到最近的一層對(duì)象上。
- 基于Function.prototype上的
apply 、 call 和 bind 調(diào)用模式,這三個(gè)方法都可以顯示的指定調(diào)用函數(shù)的 this 指向。apply 接收參數(shù)的是數(shù)組,call 接受參數(shù)列表,`` bind方法通過傳入一個(gè)對(duì)象,返回一個(gè) this 綁定了傳入對(duì)象的新函數(shù)。這個(gè)函數(shù)的 this指向除了使用 new `時(shí)會(huì)被改變,其他情況下都不會(huì)改變。若為空默認(rèn)是指向全局對(duì)象window。
原型 && 原型鏈
原型關(guān)系:
- 每個(gè) class都有顯示原型 prototype
- 每個(gè)實(shí)例都有隱式原型 _ proto_
- 實(shí)例的_ proto_指向?qū)?yīng) class 的 prototype
? 原型: 在 JS 中,每當(dāng)定義一個(gè)對(duì)象(函數(shù)也是對(duì)象)時(shí),對(duì)象中都會(huì)包含一些預(yù)定義的屬性。其中每個(gè)函數(shù)對(duì)象 都有一個(gè)prototype 屬性,這個(gè)屬性指向函數(shù)的原型對(duì)象 。
原型鏈:函數(shù)的原型鏈對(duì)象constructor默認(rèn)指向函數(shù)本身,原型對(duì)象除了有原型屬性外,為了實(shí)現(xiàn)繼承,還有一個(gè)原型鏈指針__proto__,該指針是指向上一層的原型對(duì)象,而上一層的原型對(duì)象的結(jié)構(gòu)依然類似。因此可以利用__proto__一直指向Object的原型對(duì)象上,而Object原型對(duì)象用Object.prototype.__ proto__ = null表示原型鏈頂端。如此形成了js的原型鏈繼承。同時(shí)所有的js對(duì)象都有Object的基本防范
特點(diǎn): JavaScript 對(duì)象是通過引用來傳遞的,我們創(chuàng)建的每個(gè)新對(duì)象實(shí)體中并沒有一份屬于自己的原型副本。當(dāng)我們修改原型時(shí),與之相關(guān)的對(duì)象也會(huì)繼承這一改變。
new運(yùn)算符的實(shí)現(xiàn)機(jī)制
- 首先創(chuàng)建了一個(gè)新的
空對(duì)象
設(shè)置原型 ,將對(duì)象的原型設(shè)置為函數(shù)的prototype 對(duì)象。
- 讓函數(shù)的
this 指向這個(gè)對(duì)象,執(zhí)行構(gòu)造函數(shù)的代碼(為這個(gè)新對(duì)象添加屬性)
- 判斷函數(shù)的返回值類型,如果是值類型,返回創(chuàng)建的對(duì)象。如果是引用類型,就返回這個(gè)引用類型的對(duì)象。
EventLoop 事件循環(huán)
JS 是單線程的,為了防止一個(gè)函數(shù)執(zhí)行時(shí)間過長阻塞后面的代碼,所以會(huì)先將同步代碼壓入執(zhí)行棧中,依次執(zhí)行,將異步代碼推入異步隊(duì)列,異步隊(duì)列又分為宏任務(wù)隊(duì)列和微任務(wù)隊(duì)列,因?yàn)楹耆蝿?wù)隊(duì)列的執(zhí)行時(shí)間較長,所以微任務(wù)隊(duì)列要優(yōu)先于宏任務(wù)隊(duì)列。微任務(wù)隊(duì)列的代表就是,Promise.then ,MutationObserver ,宏任務(wù)的話就是setImmediate setTimeout setInterval
JS運(yùn)行的環(huán)境。一般為瀏覽器或者Node。 在瀏覽器環(huán)境中,有JS 引擎線程和渲染線程,且兩個(gè)線程互斥。 Node環(huán)境中,只有JS 線程。 不同環(huán)境執(zhí)行機(jī)制有差異,不同任務(wù)進(jìn)入不同Event Queue隊(duì)列。 當(dāng)主程結(jié)束,先執(zhí)行準(zhǔn)備好微任務(wù),然后再執(zhí)行準(zhǔn)備好的宏任務(wù),一個(gè)輪詢結(jié)束。
瀏覽器中的事件環(huán)(Event Loop)
事件環(huán)的運(yùn)行機(jī)制是,先會(huì)執(zhí)行棧中的內(nèi)容,棧中的內(nèi)容執(zhí)行后執(zhí)行微任務(wù),微任務(wù)清空后再執(zhí)行宏任務(wù),先取出一個(gè)宏任務(wù),再去執(zhí)行微任務(wù),然后在取宏任務(wù)清微任務(wù)這樣不停的循環(huán)。
傳送門 ? # 宏任務(wù)和微任務(wù)
Node 環(huán)境中的事件環(huán)(Event Loop)
Node 是基于V8引擎的運(yùn)行在服務(wù)端的JavaScript 運(yùn)行環(huán)境,在處理高并發(fā)、I/O密集(文件操作、網(wǎng)絡(luò)操作、數(shù)據(jù)庫操作等)場(chǎng)景有明顯的優(yōu)勢(shì)。雖然用到也是V8引擎,但由于服務(wù)目的和環(huán)境不同,導(dǎo)致了它的API與原生JS有些區(qū)別,其Event Loop還要處理一些I/O,比如新的網(wǎng)絡(luò)連接等,所以Node的Event Loop(事件環(huán)機(jī)制)與瀏覽器的是不太一樣。
執(zhí)行順序如下:
timers : 計(jì)時(shí)器,執(zhí)行setTimeout和setInterval的回調(diào)
pending callbacks : 執(zhí)行延遲到下一個(gè)循環(huán)迭代的 I/O 回調(diào)
idle, prepare : 隊(duì)列的移動(dòng),僅系統(tǒng)內(nèi)部使用
poll輪詢 : 檢索新的 I/O 事件;執(zhí)行與 I/O 相關(guān)的回調(diào)。事實(shí)上除了其他幾個(gè)階段處理的事情,其他幾乎所有的異步都在這個(gè)階段處理。
check : 執(zhí)行setImmediate 回調(diào),setImmediate在這里執(zhí)行
close callbacks : 執(zhí)行close 事件的callback ,一些關(guān)閉的回調(diào)函數(shù),如:socket.on('close', ...)
setTimeout、Promise、Async/Await 的區(qū)別
-
setTimeout
settimeout的回調(diào)函數(shù)放到宏任務(wù)隊(duì)列里,等到執(zhí)行棧清空以后執(zhí)行。
-
Promise
Promise本身是同步的立即執(zhí)行函數(shù), 當(dāng)在executor中執(zhí)行resolve或者reject的時(shí)候, 此時(shí)是異步操作, 會(huì)先執(zhí)行then/catch等,當(dāng)主棧完成后,才會(huì)去調(diào)用resolve/reject中存放的方法執(zhí)行。
console.log('script start')
let promise1 = new Promise(function (resolve) {
console.log('promise1')
resolve()
console.log('promise1 end')
}).then(function () {
console.log('promise2')
})
setTimeout(function(){
console.log('settimeout')
})
console.log('script end')
// 輸出順序: script start->promise1->promise1 end->script end->promise2->settimeout
復(fù)制代碼
-
async/await
async 函數(shù)返回一個(gè) Promise 對(duì)象,當(dāng)函數(shù)執(zhí)行的時(shí)候,一旦遇到 await 就會(huì)先返回,等到觸發(fā)的異步操作完成,再執(zhí)行函數(shù)體內(nèi)后面的語句??梢岳斫鉃?,是讓出了線程,跳出了 async 函數(shù)體。
async function async1(){
console.log('async1 start');
await async2();
console.log('async1 end')
}
async function async2(){
console.log('async2')
}
console.log('script start');
async1();
console.log('script end')
// 輸出順序:script start->async1 start->async2->script end->async1 end
復(fù)制代碼
傳送門 ? # JavaScript Promise 專題
Async/Await 如何通過同步的方式實(shí)現(xiàn)異步
Async/Await就是一個(gè)自執(zhí)行的generate函數(shù)。利用generate函數(shù)的特性把異步的代碼寫成“同步”的形式,第一個(gè)請(qǐng)求的返回值作為后面一個(gè)請(qǐng)求的參數(shù),其中每一個(gè)參數(shù)都是一個(gè)promise對(duì)象.
介紹節(jié)流防抖原理、區(qū)別以及應(yīng)用
節(jié)流 :事件觸發(fā)后,規(guī)定時(shí)間內(nèi),事件處理函數(shù)不能再次被調(diào)用。也就是說在規(guī)定的時(shí)間內(nèi),函數(shù)只能被調(diào)用一次,且是最先被觸發(fā)調(diào)用的那次。
防抖 :多次觸發(fā)事件,事件處理函數(shù)只能執(zhí)行一次,并且是在觸發(fā)操作結(jié)束時(shí)執(zhí)行。也就是說,當(dāng)一個(gè)事件被觸發(fā)準(zhǔn)備執(zhí)行事件函數(shù)前,會(huì)等待一定的時(shí)間(這時(shí)間是碼農(nóng)自己去定義的,比如 1 秒),如果沒有再次被觸發(fā),那么就執(zhí)行,如果被觸發(fā)了,那就本次作廢,重新從新觸發(fā)的時(shí)間開始計(jì)算,并再次等待 1 秒,直到能最終執(zhí)行!
使用場(chǎng)景 :
節(jié)流:滾動(dòng)加載更多、搜索框搜的索聯(lián)想功能、高頻點(diǎn)擊、表單重復(fù)提交……
防抖:搜索框搜索輸入,并在輸入完以后自動(dòng)搜索、手機(jī)號(hào),郵箱驗(yàn)證輸入檢測(cè)、窗口大小 resize 變化后,再重新渲染。
/**
* 節(jié)流函數(shù) 一個(gè)函數(shù)執(zhí)行一次后,只有大于設(shè)定的執(zhí)行周期才會(huì)執(zhí)行第二次。有個(gè)需要頻繁觸發(fā)的函數(shù),出于優(yōu)化性能的角度,在規(guī)定時(shí)間內(nèi),只讓函數(shù)觸發(fā)的第一次生效,后面的不生效。
* @param fn要被節(jié)流的函數(shù)
* @param delay規(guī)定的時(shí)間
*/
function throttle(fn, delay) {
//記錄上一次函數(shù)觸發(fā)的時(shí)間
var lastTime = 0;
return function(){
//記錄當(dāng)前函數(shù)觸發(fā)的時(shí)間
var nowTime = Date.now();
if(nowTime - lastTime > delay){
//修正this指向問題
fn.call(this);
//同步執(zhí)行結(jié)束時(shí)間
lastTime = nowTime;
}
}
}
document.onscroll = throttle(function () {
console.log('scllor事件被觸發(fā)了' + Date.now());
}, 200);
/**
* 防抖函數(shù) 一個(gè)需要頻繁觸發(fā)的函數(shù),在規(guī)定時(shí)間內(nèi),只讓最后一次生效,前面的不生效
* @param fn要被節(jié)流的函數(shù)
* @param delay規(guī)定的時(shí)間
*/
function debounce(fn, delay) {
//記錄上一次的延時(shí)器
var timer = null;
return function () {
//清除上一次的演示器
clearTimeout(timer);
//重新設(shè)置新的延時(shí)器
timer = setTimeout(()=>{
//修正this指向問題
fn.apply(this);
}, delay);
}
}
document.getElementById('btn').onclick = debounce(function () {
console.log('按鈕被點(diǎn)擊了' + Date.now());
}, 1000);
復(fù)制代碼
簡述MVVM
什么是MVVM?
視圖模型雙向綁定 ,是Model-View-ViewModel 的縮寫,也就是把MVC 中的Controller 演變成ViewModel。Model 層代表數(shù)據(jù)模型,View 代表UI組件,ViewModel 是View 和Model 層的橋梁,數(shù)據(jù)會(huì)綁定到viewModel 層并自動(dòng)將數(shù)據(jù)渲染到頁面中,視圖變化的時(shí)候會(huì)通知viewModel 層更新數(shù)據(jù)。以前是操作DOM結(jié)構(gòu)更新視圖,現(xiàn)在是數(shù)據(jù)驅(qū)動(dòng)視圖 。
MVVM的優(yōu)點(diǎn):
1.低耦合 。視圖(View)可以獨(dú)立于Model變化和修改,一個(gè)Model可以綁定到不同的View上,當(dāng)View變化的時(shí)候Model可以不變化,當(dāng)Model變化的時(shí)候View也可以不變;
2.可重用性 。你可以把一些視圖邏輯放在一個(gè)Model里面,讓很多View重用這段視圖邏輯。
3.獨(dú)立開發(fā) 。開發(fā)人員可以專注于業(yè)務(wù)邏輯和數(shù)據(jù)的開發(fā)(ViewModel),設(shè)計(jì)人員可以專注于頁面設(shè)計(jì)。
4.可測(cè)試 。
Vue底層實(shí)現(xiàn)原理
vue.js是采用數(shù)據(jù)劫持結(jié)合發(fā)布者-訂閱者模式的方式,通過Object.defineProperty()來劫持各個(gè)屬性的setter和getter,在數(shù)據(jù)變動(dòng)時(shí)發(fā)布消息給訂閱者,觸發(fā)相應(yīng)的監(jiān)聽回調(diào)
Vue是一個(gè)典型的MVVM框架,模型(Model)只是普通的javascript對(duì)象,修改它則試圖(View)會(huì)自動(dòng)更新。這種設(shè)計(jì)讓狀態(tài)管理變得非常簡單而直觀
Observer(數(shù)據(jù)監(jiān)聽器) : Observer的核心是通過Object.defineProprtty()來監(jiān)聽數(shù)據(jù)的變動(dòng),這個(gè)函數(shù)內(nèi)部可以定義setter和getter,每當(dāng)數(shù)據(jù)發(fā)生變化,就會(huì)觸發(fā)setter。這時(shí)候Observer就要通知訂閱者,訂閱者就是Watcher
Watcher(訂閱者) : Watcher訂閱者作為Observer和Compile之間通信的橋梁,主要做的事情是:
- 在自身實(shí)例化時(shí)往屬性訂閱器(dep)里面添加自己
- 自身必須有一個(gè)update()方法
- 待屬性變動(dòng)dep.notice()通知時(shí),能調(diào)用自身的update()方法,并觸發(fā)Compile中綁定的回調(diào)
Compile(指令解析器) : Compile主要做的事情是解析模板指令,將模板中變量替換成數(shù)據(jù),然后初始化渲染頁面視圖,并將每個(gè)指令對(duì)應(yīng)的節(jié)點(diǎn)綁定更新函數(shù),添加鑒定數(shù)據(jù)的訂閱者,一旦數(shù)據(jù)有變動(dòng),收到通知,更新試圖
談?wù)剬?duì)vue生命周期的理解?
每個(gè)Vue 實(shí)例在創(chuàng)建時(shí)都會(huì)經(jīng)過一系列的初始化過程,vue 的生命周期鉤子,就是說在達(dá)到某一階段或條件時(shí)去觸發(fā)的函數(shù),目的就是為了完成一些動(dòng)作或者事件
create階段 :vue實(shí)例被創(chuàng)建
beforeCreate : 創(chuàng)建前,此時(shí)data和methods中的數(shù)據(jù)都還沒有初始化
created : 創(chuàng)建完畢,data中有值,未掛載
mount階段 : vue實(shí)例被掛載到真實(shí)DOM節(jié)點(diǎn)
beforeMount :可以發(fā)起服務(wù)端請(qǐng)求,去數(shù)據(jù)
mounted : 此時(shí)可以操作DOM
update階段 :當(dāng)vue實(shí)例里面的data數(shù)據(jù)變化時(shí),觸發(fā)組件的重新渲染
beforeUpdate :更新前
updated :更新后
destroy階段 :vue實(shí)例被銷毀
beforeDestroy :實(shí)例被銷毀前,此時(shí)可以手動(dòng)銷毀一些方法
destroyed :銷毀后
組件生命周期
生命周期(父子組件) 父組件beforeCreate --> 父組件created --> 父組件beforeMount --> 子組件beforeCreate --> 子組件created --> 子組件beforeMount --> 子組件 mounted --> 父組件mounted -->父組件beforeUpdate -->子組件beforeDestroy--> 子組件destroyed --> 父組件updated
加載渲染過程 父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
掛載階段 父created->子created->子mounted->父mounted
父組件更新階段 父beforeUpdate->父updated
子組件更新階段 父beforeUpdate->子beforeUpdate->子updated->父updated
銷毀階段 父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
computed與watch
通俗來講,既能用 computed 實(shí)現(xiàn)又可以用 watch 監(jiān)聽來實(shí)現(xiàn)的功能,推薦用 computed, 重點(diǎn)在于 computed 的緩存功能 computed 計(jì)算屬性是用來聲明式的描述一個(gè)值依賴了其它的值,當(dāng)所依賴的值或者變量 改變時(shí),計(jì)算屬性也會(huì)跟著改變; watch 監(jiān)聽的是已經(jīng)在 data 中定義的變量,當(dāng)該變量變化時(shí),會(huì)觸發(fā) watch 中的方法。
watch 屬性監(jiān)聽 是一個(gè)對(duì)象,鍵是需要觀察的屬性,值是對(duì)應(yīng)回調(diào)函數(shù),主要用來監(jiān)聽某些特定數(shù)據(jù)的變化,從而進(jìn)行某些具體的業(yè)務(wù)邏輯操作,監(jiān)聽屬性的變化,需要在數(shù)據(jù)變化時(shí)執(zhí)行異步或開銷較大的操作時(shí)使用
computed 計(jì)算屬性 屬性的結(jié)果會(huì)被緩存 ,當(dāng)computed 中的函數(shù)所依賴的屬性沒有發(fā)生改變的時(shí)候,那么調(diào)用當(dāng)前函數(shù)的時(shí)候結(jié)果會(huì)從緩存中讀取。除非依賴的響應(yīng)式屬性變化時(shí)才會(huì)重新計(jì)算,主要當(dāng)做屬性來使用 computed 中的函數(shù)必須用return 返回最終的結(jié)果 computed 更高效,優(yōu)先使用。data 不改變,computed 不更新。
使用場(chǎng)景 computed :當(dāng)一個(gè)屬性受多個(gè)屬性影響的時(shí)候使用,例:購物車商品結(jié)算功能 watch :當(dāng)一條數(shù)據(jù)影響多條數(shù)據(jù)的時(shí)候使用,例:搜索數(shù)據(jù)
組件中的data為什么是一個(gè)函數(shù)?
1.一個(gè)組件被復(fù)用多次的話,也就會(huì)創(chuàng)建多個(gè)實(shí)例。本質(zhì)上,這些實(shí)例用的都是同一個(gè)構(gòu)造函數(shù)。 2.如果data是對(duì)象的話,對(duì)象屬于引用類型,會(huì)影響到所有的實(shí)例。所以為了保證組件不同的實(shí)例之間data不沖突,data必須是一個(gè)函數(shù)。
為什么v-for和v-if不建議用在一起
1.當(dāng) v-for 和 v-if 處于同一個(gè)節(jié)點(diǎn)時(shí),v-for 的優(yōu)先級(jí)比 v-if 更高,這意味著 v-if 將分別重復(fù)運(yùn)行于每個(gè) v-for 循環(huán)中。如果要遍歷的數(shù)組很大,而真正要展示的數(shù)據(jù)很少時(shí),這將造成很大的性能浪費(fèi)(Vue2.x)
2.這種場(chǎng)景建議使用 computed,先對(duì)數(shù)據(jù)進(jìn)行過濾
注意:3.x 版本中 v-if 總是優(yōu)先于 v-for 生效。由于語法上存在歧義,建議避免在同一元素上同時(shí)使用兩者。比起在模板層面管理相關(guān)邏輯,更好的辦法是通過創(chuàng)建計(jì)算屬性篩選出列表,并以此創(chuàng)建可見元素。
解惑傳送門 ? # v-if 與 v-for 的優(yōu)先級(jí)對(duì)比非兼容
React/Vue 項(xiàng)目中 key 的作用
-
key的作用是為了在diff算法執(zhí)行時(shí)更快的找到對(duì)應(yīng)的節(jié)點(diǎn),提高diff速度,更高效的更新虛擬DOM ;
vue和react都是采用diff算法來對(duì)比新舊虛擬節(jié)點(diǎn),從而更新節(jié)點(diǎn)。在vue的diff函數(shù)中,會(huì)根據(jù)新節(jié)點(diǎn)的key去對(duì)比舊節(jié)點(diǎn)數(shù)組中的key,從而找到相應(yīng)舊節(jié)點(diǎn)。如果沒找到就認(rèn)為是一個(gè)新增節(jié)點(diǎn)。而如果沒有key,那么就會(huì)采用遍歷查找的方式去找到對(duì)應(yīng)的舊節(jié)點(diǎn)。一種一個(gè)map映射,另一種是遍歷查找。相比而言。map映射的速度更快。
-
為了在數(shù)據(jù)變化時(shí)強(qiáng)制更新組件,以避免“就地復(fù)用” 帶來的副作用。
當(dāng) Vue.js 用 v-for 更新已渲染過的元素列表時(shí),它默認(rèn)用“就地復(fù)用”策略。如果數(shù)據(jù)項(xiàng)的順序被改變,Vue 將不會(huì)移動(dòng) DOM 元素來匹配數(shù)據(jù)項(xiàng)的順序,而是簡單復(fù)用此處每個(gè)元素,并且確保它在特定索引下顯示已被渲染過的每個(gè)元素。重復(fù)的key會(huì)造成渲染錯(cuò)誤。
vue組件的通信方式
-
props /$emit 父子組件通信
父->子props ,子->父 $on、$emit 獲取父子組件實(shí)例 parent、children Ref 獲取實(shí)例的方式調(diào)用組件的屬性或者方法 父->子孫 Provide、inject 官方不推薦使用,但是寫組件庫時(shí)很常用
-
$emit /$on 自定義事件 兄弟組件通信
Event Bus 實(shí)現(xiàn)跨組件通信 Vue.prototype.$bus = new Vue() 自定義事件
-
vuex 跨級(jí)組件通信
Vuex、$attrs、$listeners Provide、inject
nextTick的實(shí)現(xiàn)
nextTick 是Vue 提供的一個(gè)全局API ,是在下次DOM 更新循環(huán)結(jié)束之后執(zhí)行延遲回調(diào),在修改數(shù)據(jù)之后使用$nextTick ,則可以在回調(diào)中獲取更新后的DOM ;
- Vue在更新DOM時(shí)是異步執(zhí)行的。只要偵聽到數(shù)據(jù)變化,
Vue 將開啟1個(gè)隊(duì)列,并緩沖在同一事件循環(huán)中發(fā)生的所有數(shù)據(jù)變更。如果同一個(gè)watcher 被多次觸發(fā),只會(huì)被推入到隊(duì)列中-次。這種在緩沖時(shí)去除重復(fù)數(shù)據(jù)對(duì)于避免不必要的計(jì)算和DOM 操作是非常重要的。nextTick 方法會(huì)在隊(duì)列中加入一個(gè)回調(diào)函數(shù),確保該函數(shù)在前面的dom操作完成后才調(diào)用;
- 比如,我在干什么的時(shí)候就會(huì)使用nextTick,傳一個(gè)回調(diào)函數(shù)進(jìn)去,在里面執(zhí)行dom操作即可;
- 我也有簡單了解
nextTick 實(shí)現(xiàn),它會(huì)在callbacks 里面加入我們傳入的函數(shù),然后用timerFunc 異步方式調(diào)用它們,首選的異步方式會(huì)是Promise 。這讓我明白了為什么可以在nextTick 中看到dom 操作結(jié)果。
nextTick的實(shí)現(xiàn)原理是什么?
在下次 DOM 更新循環(huán)結(jié)束之后執(zhí)行延遲回調(diào),在修改數(shù)據(jù)之后立即使用 nextTick 來獲取更新后的 DOM。 nextTick主要使用了宏任務(wù)和微任務(wù)。 根據(jù)執(zhí)行環(huán)境分別嘗試采用Promise、MutationObserver、setImmediate,如果以上都不行則采用setTimeout定義了一個(gè)異步方法,多次調(diào)用nextTick會(huì)將方法存入隊(duì)列中,通過這個(gè)異步方法清空當(dāng)前隊(duì)列。
使用過插槽么?用的是具名插槽還是匿名插槽或作用域插槽
vue中的插槽是一個(gè)非常好用的東西slot說白了就是一個(gè)占位的 在vue當(dāng)中插槽包含三種一種是默認(rèn)插槽(匿名)一種是具名插槽還有一種就是作用域插槽 匿名插槽就是沒有名字的只要默認(rèn)的都填到這里具名插槽指的是具有名字的
keep-alive的實(shí)現(xiàn)
作用:實(shí)現(xiàn)組件緩存,保持這些組件的狀態(tài),以避免反復(fù)渲染導(dǎo)致的性能問題。 需要緩存組件 頻繁切換,不需要重復(fù)渲染
場(chǎng)景:tabs標(biāo)簽頁 后臺(tái)導(dǎo)航,vue性能優(yōu)化
原理:Vue.js 內(nèi)部將DOM 節(jié)點(diǎn)抽象成了一個(gè)個(gè)的VNode 節(jié)點(diǎn),keep-alive 組件的緩存也是基于VNode 節(jié)點(diǎn)的而不是直接存儲(chǔ)DOM 結(jié)構(gòu)。它將滿足條件(pruneCache與pruneCache) 的組件在cache 對(duì)象中緩存起來,在需要重新渲染的時(shí)候再將vnode 節(jié)點(diǎn)從cache 對(duì)象中取出并渲染。
mixin
mixin 項(xiàng)目變得復(fù)雜的時(shí)候,多個(gè)組件間有重復(fù)的邏輯就會(huì)用到mixin
多個(gè)組件有相同的邏輯,抽離出來
mixin并不是完美的解決方案,會(huì)有一些問題
vue3提出的Composition API旨在解決這些問題【追求完美是要消耗一定的成本的,如開發(fā)成本】
場(chǎng)景:PC端新聞列表和詳情頁一樣的右側(cè)欄目,可以使用mixin進(jìn)行混合
劣勢(shì):1.變量來源不明確,不利于閱讀
2.多mixin可能會(huì)造成命名沖突 3.mixin和組件可能出現(xiàn)多對(duì)多的關(guān)系,使得項(xiàng)目復(fù)雜度變高
Vuex的理解及使用場(chǎng)景
Vuex 是一個(gè)專為 Vue 應(yīng)用程序開發(fā)的狀態(tài)管理模式。每一個(gè) Vuex 應(yīng)用的核心就是 store(倉庫)。
- Vuex 的狀態(tài)存儲(chǔ)是響應(yīng)式的;當(dāng) Vue 組件從 store 中讀取狀態(tài)的時(shí)候,
若 store 中的狀態(tài)發(fā)生變化,那么相應(yīng)的組件也會(huì)相應(yīng)地得到高效更新 2. 改變 store 中的狀態(tài)的唯一途徑就是顯式地提交 (commit) mutation, 這樣使得我們可以方便地跟蹤每一個(gè)狀態(tài)的變化 Vuex主要包括以下幾個(gè)核心模塊:
- State:定義了應(yīng)用的狀態(tài)數(shù)據(jù)
- Getter:在 store 中定義“getter”(可以認(rèn)為是 store 的計(jì)算屬性),
就像計(jì)算屬性一樣,getter 的返回值會(huì)根據(jù)它的依賴被緩存起來, 且只有當(dāng)它的依賴值發(fā)生了改變才會(huì)被重新計(jì)算 3. Mutation:是唯一更改 store 中狀態(tài)的方法,且必須是同步函數(shù) 4. Action:用于提交 mutation,而不是直接變更狀態(tài),可以包含任意異步操作 5. Module:允許將單一的 Store 拆分為多個(gè) store 且同時(shí)保存在單一的狀態(tài)樹中
hooks用過嗎?聊聊react中class組件和函數(shù)組件的區(qū)別
類組件是使用ES6 的 class 來定義的組件。 函數(shù)組件是接收一個(gè)單一的 props 對(duì)象并返回一個(gè)React元素。
關(guān)于React的兩套API(類(class)API 和基于函數(shù)的鉤子(hooks) API)。官方推薦使用鉤子(函數(shù)),而不是類。因?yàn)殂^子更簡潔,代碼量少,用起來比較'輕',而類比較'重'。而且,鉤子是函數(shù),更符合 React 函數(shù)式的本質(zhì)。
函數(shù)一般來說,只應(yīng)該做一件事,就是返回一個(gè)值。 如果你有多個(gè)操作,每個(gè)操作應(yīng)該寫成一個(gè)單獨(dú)的函數(shù)。而且,數(shù)據(jù)的狀態(tài)應(yīng)該與操作方法分離。根據(jù)函數(shù)這種理念,React 的函數(shù)組件只應(yīng)該做一件事情:返回組件的 HTML 代碼,而沒有其他的功能。函數(shù)的返回結(jié)果只依賴于它的參數(shù)。不改變函數(shù)體外部數(shù)據(jù)、函數(shù)執(zhí)行過程里面沒有副作用。
類(class)是數(shù)據(jù)和邏輯的封裝。 也就是說,組件的狀態(tài)和操作方法是封裝在一起的。如果選擇了類的寫法,就應(yīng)該把相關(guān)的數(shù)據(jù)和操作,都寫在同一個(gè) class 里面。
類組件的缺點(diǎn) :
大型組件很難拆分和重構(gòu),也很難測(cè)試。
業(yè)務(wù)邏輯分散在組件的各個(gè)方法之中,導(dǎo)致重復(fù)邏輯或關(guān)聯(lián)邏輯。
組件類引入了復(fù)雜的編程模式,比如 render props 和高階組件。
難以理解的 class,理解 JavaScript 中 this 的工作方式。
區(qū)別:
函數(shù)組件的性能比類組件的性能要高,因?yàn)轭惤M件使用的時(shí)候要實(shí)例化,而函數(shù)組件直接執(zhí)行函數(shù)取返回結(jié)果即可。
1.狀態(tài)的有無
hooks出現(xiàn)之前,函數(shù)組件沒有實(shí)例 ,沒有生命周期 ,沒有state ,沒有this ,所以我們稱函數(shù)組件為無狀態(tài)組件。 hooks出現(xiàn)之前,react中的函數(shù)組件通常只考慮負(fù)責(zé)UI的渲染,沒有自身的狀態(tài)沒有業(yè)務(wù)邏輯代碼,是一個(gè)純函數(shù)。它的輸出只由參數(shù)props決定,不受其他任何因素影響。
2.調(diào)用方式的不同
函數(shù)組件重新渲染,將重新調(diào)用組件方法返回新的react元素。類組件重新渲染將new一個(gè)新的組件實(shí)例,然后調(diào)用render類方法返回react元素,這也說明為什么類組件中this是可變的。
3.因?yàn)檎{(diào)用方式不同,在函數(shù)組件使用中會(huì)出現(xiàn)問題
在操作中改變狀態(tài)值,類組件可以獲取最新的狀態(tài)值,而函數(shù)組件則會(huì)按照順序返回狀態(tài)值
React Hooks(鉤子的作用)
Hook 是 React 16.8 的新增特性。它可以讓你在不編寫 class 的情況下使用 state 以及其他的 React 特性。
React Hooks的幾個(gè)常用鉤子:
useState() //狀態(tài)鉤子
useContext() //共享狀態(tài)鉤子
useReducer() //action 鉤子
useEffect() //副作用鉤子
還有幾個(gè)不常見的大概的說下,后續(xù)會(huì)專門寫篇文章描述下
-
1.useCallback 記憶函數(shù) 一般把函數(shù)式組件理解為class組件render函數(shù)的語法糖,所以每次重新渲染的時(shí)候,函數(shù)式組件內(nèi)部所有的代碼都會(huì)重新執(zhí)行一遍。而有了 useCallback 就不一樣了,你可以通過 useCallback 獲得一個(gè)記憶后的函數(shù)。
function App() {
const memoizedHandleClick = useCallback(() => {
console.log('Click happened')
}, []); // 空數(shù)組代表無論什么情況下該函數(shù)都不會(huì)發(fā)生改變
return <SomeComponent onClick={memoizedHandleClick}>Click Me</SomeComponent>;
}
復(fù)制代碼
第二個(gè)參數(shù)傳入一個(gè)數(shù)組,數(shù)組中的每一項(xiàng)一旦值或者引用發(fā)生改變,useCallback 就會(huì)重新返回一個(gè)新的記憶函數(shù)提供給后面進(jìn)行渲染。
-
2.useMemo 記憶組件 useCallback 的功能完全可以由 useMemo 所取代,如果你想通過使用 useMemo 返回一個(gè)記憶函數(shù)也是完全可以的。 唯一的區(qū)別是:useCallback 不會(huì)執(zhí)行第一個(gè)參數(shù)函數(shù),而是將它返回給你,而 useMemo 會(huì)執(zhí)行第一個(gè)函數(shù)并且將函數(shù)執(zhí)行結(jié)果返回給你。
所以 useCallback 常用記憶事件函數(shù),生成記憶后的事件函數(shù)并傳遞給子組件使用。而 useMemo 更適合經(jīng)過函數(shù)計(jì)算得到一個(gè)確定的值,比如記憶組件。
-
3.useRef 保存引用值
useRef 跟 createRef 類似,都可以用來生成對(duì) DOM 對(duì)象的引用。useRef 返回的值傳遞給組件或者 DOM 的 ref 屬性,就可以通過 ref.current 值訪問組件或真實(shí)的 DOM 節(jié)點(diǎn),重點(diǎn)是組件也是可以訪問到的,從而可以對(duì) DOM 進(jìn)行一些操作,比如監(jiān)聽事件等等。
-
4.useImperativeHandle 穿透 Ref
通過 useImperativeHandle 用于讓父組件獲取子組件內(nèi)的索引
-
5.useLayoutEffect 同步執(zhí)行副作用
大部分情況下,使用 useEffect 就可以幫我們處理組件的副作用,但是如果想要同步調(diào)用一些副作用,比如對(duì) DOM 的操作,就需要使用 useLayoutEffect,useLayoutEffect 中的副作用會(huì)在 DOM 更新之后同步執(zhí)行。
useEffect和useLayoutEffect有什么區(qū)別:簡單來說就是調(diào)用時(shí)機(jī)不同,useLayoutEffect和原來componentDidMount&componentDidUpdate一致,在react完成DOM更新后馬上同步調(diào)用的代碼,會(huì)阻塞頁面渲染。而useEffect是會(huì)在整個(gè)頁面渲染完才會(huì)調(diào)用的代碼。官方建議優(yōu)先使用useEffect
React 組件通信方式
react組件間通信常見的幾種情況:
-
- 父組件向子組件通信
-
- 子組件向父組件通信
-
- 跨級(jí)組件通信
-
- 非嵌套關(guān)系的組件通信
1)父組件向子組件通信
父組件通過 props 向子組件傳遞需要的信息。父?jìng)髯邮窃诟附M件中直接綁定一個(gè)正常的屬性,這個(gè)屬性就是指具體的值,在子組件中,用props就可以獲取到這個(gè)值
// 子組件: Child
const Child = props =>{
return <p>{props.name}</p>
}
// 父組件 Parent
const Parent = ()=>{
return <Child name='京程一燈'></Child>
}
復(fù)制代碼
2)子組件向父組件通信
props+回調(diào)的方式,使用公共組件進(jìn)行狀態(tài)提升。子傳父是先在父組件上綁定屬性設(shè)置為一個(gè)函數(shù),當(dāng)子組件需要給父組件傳值的時(shí)候,則通過props調(diào)用該函數(shù)將參數(shù)傳入到該函數(shù)當(dāng)中,此時(shí)就可以在父組件中的函數(shù)中接收到該參數(shù)了,這個(gè)參數(shù)則為子組件傳過來的值
// 子組件: Child
const Child = props =>{
const cb = msg =>{
return ()=>{
props.callback(msg)
}
}
return (
<button onClick={cb('京程一燈歡迎你!')}>京程一燈歡迎你</button>
)
}
// 父組件 Parent
class Parent extends Component {
callback(msg){
console.log(msg)
}
render(){
return <Child callback={this.callback.bind(this)}></Child>
}
}
復(fù)制代碼
3)跨級(jí)組件通信
即父組件向子組件的子組件通信,向更深層子組件通信。
- 使用props,利用中間組件層層傳遞,但是如果父組件結(jié)構(gòu)較深,那么中間每一層組件都要去傳遞props,增加了復(fù)雜度,并且這些props并不是中間組件自己需要的。
- 使用context,context相當(dāng)于一個(gè)大容器,我們可以把要通信的內(nèi)容放在這個(gè)容器中,這樣不管嵌套多深,都可以隨意取用,對(duì)于跨越多層的全局?jǐn)?shù)據(jù)可以使用context實(shí)現(xiàn)。
// context方式實(shí)現(xiàn)跨級(jí)組件通信
// Context 設(shè)計(jì)目的是為了共享那些對(duì)于一個(gè)組件樹而言是“全局”的數(shù)據(jù)
const BatteryContext = createContext();
// 子組件的子組件
class GrandChild extends Component {
render(){
return (
<BatteryContext.Consumer>
{
color => <h1 style={{'color':color}}>我是紅色的:{color}</h1>
}
</BatteryContext.Consumer>
)
}
}
// 子組件
const Child = () =>{
return (
<GrandChild/>
)
}
// 父組件
class Parent extends Component {
state = {
color:'red'
}
render(){
const {color} = this.state
return (
<BatteryContext.Provider value={color}>
<Child></Child>
</BatteryContext.Provider>
)
}
}
復(fù)制代碼
4)非嵌套關(guān)系的組件通信
即沒有任何包含關(guān)系的組件,包括兄弟組件以及不在同一個(gè)父級(jí)中的非兄弟組件。
-
- 可以使用自定義事件通信(發(fā)布訂閱模式),使用pubsub-js
-
- 可以通過redux等進(jìn)行全局狀態(tài)管理
-
- 如果是兄弟組件通信,可以找到這兩個(gè)兄弟節(jié)點(diǎn)共同的父節(jié)點(diǎn), 結(jié)合父子間通信方式進(jìn)行通信。
-
- 也可以new一個(gè) Vue 的 EventBus,進(jìn)行事件監(jiān)聽,一邊執(zhí)行監(jiān)聽,一邊執(zhí)行新增 VUE的eventBus 就是發(fā)布訂閱模式,是可以在React中使用的;
setState 既存在異步情況也存在同步情況
1.異步情況 在React事件當(dāng)中是異步操作
2.同步情況 如果是在setTimeout事件或者自定義的dom事件 中,都是同步的
//setTimeout事件
import React,{ Component } from 'react';
class Count extends Component{
constructor(props){
super(props);
this.state = {
count:0
}
}
render(){
return (
<>
<p>count:{this.state.count}</p>
<button onClick={this.btnAction}>增加</button>
</>
)
}
btnAction = ()=>{
//不能直接修改state,需要通過setState進(jìn)行修改
//同步
setTimeout(()=>{
this.setState({
count: this.state.count + 1
});
console.log(this.state.count);
})
}
}
export default Count;
復(fù)制代碼
//自定義dom事件
import React,{ Component } from 'react';
class Count extends Component{
constructor(props){
super(props);
this.state = {
count:0
}
}
render(){
return (
<>
<p>count:{this.state.count}</p>
<button id='btn'>綁定點(diǎn)擊事件</button>
</>
)
}
componentDidMount(){
//自定義dom事件,也是同步修改
document.querySelector('#btn').addEventListener('click',()=>{
this.setState({
count: this.state.count + 1
});
console.log(this.state.count);
});
}
}
export default Count;
復(fù)制代碼
生命周期
安裝
當(dāng)組件的實(shí)例被創(chuàng)建并插入到 DOM 中時(shí),這些方法按以下順序調(diào)用:
constructor()
static getDerivedStateFromProps()
render()
componentDidMount()
更新中
更新可能由道具或狀態(tài)的更改引起。當(dāng)重新渲染組件時(shí),這些方法按以下順序調(diào)用:
static getDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate()
componentDidUpdate()
卸載
當(dāng)組件從 DOM 中移除時(shí)調(diào)用此方法:
componentWillUnmount()
復(fù)制代碼
說一下 react-fiber
1)背景
react-fiber 產(chǎn)生的根本原因,是大量的同步計(jì)算任務(wù)阻塞了瀏覽器的 UI 渲染 。默認(rèn)情況下,JS 運(yùn)算、頁面布局和頁面繪制都是運(yùn)行在瀏覽器的主線程當(dāng)中,他們之間是互斥的關(guān)系。如果 JS 運(yùn)算持續(xù)占用主線程,頁面就沒法得到及時(shí)的更新。當(dāng)我們調(diào)用setState 更新頁面的時(shí)候,React 會(huì)遍歷應(yīng)用的所有節(jié)點(diǎn),計(jì)算出差異,然后再更新 UI。如果頁面元素很多,整個(gè)過程占用的時(shí)機(jī)就可能超過 16 毫秒,就容易出現(xiàn)掉幀的現(xiàn)象。
2)實(shí)現(xiàn)原理
Fiber 其實(shí)指的是一種數(shù)據(jù)結(jié)構(gòu),它可以用一個(gè)純 JS 對(duì)象來表示 :
const fiber = {
stateNode, // 節(jié)點(diǎn)實(shí)例
child, // 子節(jié)點(diǎn)
sibling, // 兄弟節(jié)點(diǎn)
return, // 父節(jié)點(diǎn)
}
復(fù)制代碼
-
為了實(shí)現(xiàn)不卡頓,就需要有一個(gè)調(diào)度器 (Scheduler) 來進(jìn)行任務(wù)分配。優(yōu)先級(jí)高的任務(wù)(如鍵盤輸入)可以打斷優(yōu)先級(jí)低的任務(wù)(如Diff)的執(zhí)行,從而更快的生效。任務(wù)的優(yōu)先級(jí)有六種:
- synchronous,與之前的Stack Reconciler操作一樣,同步執(zhí)行
- task,在next tick之前執(zhí)行
- animation,下一幀之前執(zhí)行
- high,在不久的將來立即執(zhí)行
- low,稍微延遲執(zhí)行也沒關(guān)系
- offscreen,下一次render時(shí)或scroll時(shí)才執(zhí)行
-
Fiber Reconciler(react )執(zhí)行過程分為2個(gè)階段:
- 階段一,生成 Fiber 樹,得出需要更新的節(jié)點(diǎn)信息。這一步是一個(gè)漸進(jìn)的過程,可以被打斷。階段一可被打斷的特性,讓優(yōu)先級(jí)更高的任務(wù)先執(zhí)行,從框架層面大大降低了頁面掉幀的概率。
- 階段二,將需要更新的節(jié)點(diǎn)一次過批量更新,這個(gè)過程不能被打斷。
-
Fiber樹:React 在 render 第一次渲染時(shí),會(huì)通過 React.createElement 創(chuàng)建一顆 Element 樹,可以稱之為 Virtual DOM Tree,由于要記錄上下文信息,加入了 Fiber,每一個(gè) Element 會(huì)對(duì)應(yīng)一個(gè) Fiber Node,將 Fiber Node 鏈接起來的結(jié)構(gòu)成為 Fiber Tree。Fiber Tree 一個(gè)重要的特點(diǎn)是鏈表結(jié)構(gòu),將遞歸遍歷編程循環(huán)遍歷,然后配合 requestIdleCallback API, 實(shí)現(xiàn)任務(wù)拆分、中斷與恢復(fù)。
從Stack Reconciler到Fiber Reconciler,源碼層面其實(shí)就是干了一件遞歸改循環(huán)的事情
傳送門 ?# 深入了解 Fiber
Portals
Portals 提供了一種一流的方式來將子組件渲染到存在于父組件的 DOM 層次結(jié)構(gòu)之外的 DOM 節(jié)點(diǎn)中。結(jié)構(gòu)不受外界的控制的情況下就可以使用portals進(jìn)行創(chuàng)建
何時(shí)要使用異步組件?如和使用異步組件
- 加載大組件的時(shí)候
- 路由異步加載的時(shí)候
react 中要配合 Suspense 使用
// 異步懶加載
const Box = lazy(()=>import('./components/Box'));
// 使用組件的時(shí)候要用suspense進(jìn)行包裹
<Suspense fallback={<div>loading...</div>}>
{show && <Box/>}
</Suspense>
復(fù)制代碼
React 事件綁定原理
React并不是將click事件綁在該div的真實(shí)DOM上,而是在document處監(jiān)聽所有支持的事件 ,當(dāng)事件發(fā)生并冒泡至document處時(shí),React將事件內(nèi)容封裝并交由真正的處理函數(shù)運(yùn)行。這樣的方式不僅減少了內(nèi)存消耗,還能在組件掛載銷毀時(shí)統(tǒng)一訂閱和移除事件。
另外冒泡到 document 上的事件也不是原生瀏覽器事件,而是 React 自己實(shí)現(xiàn)的合成事件(SyntheticEvent)。因此我們?nèi)绻幌胍录芭莸脑挘{(diào)用 event.stopPropagation 是無效的,而應(yīng)該調(diào)用 event.preventDefault 。
webpack
webpack 做過哪些優(yōu)化,開發(fā)效率方面、打包策略方面等等
1)優(yōu)化 Webpack 的構(gòu)建速度
- 使用高版本的 Webpack (使用webpack4)
- 多線程/多實(shí)例構(gòu)建:HappyPack(不維護(hù)了)、thread-loader
- 縮小打包作用域:
- exclude/include (確定 loader 規(guī)則范圍)
- resolve.modules 指明第三方模塊的絕對(duì)路徑 (減少不必要的查找)
- resolve.extensions 盡可能減少后綴嘗試的可能性
- noParse 對(duì)完全不需要解析的庫進(jìn)行忽略 (不去解析但仍會(huì)打包到 bundle 中,注意被忽略掉的文件里不應(yīng)該包含 import、require、define 等模塊化語句)
- IgnorePlugin (完全排除模塊)
- 合理使用alias
- 充分利用緩存提升二次構(gòu)建速度:
- babel-loader 開啟緩存
- terser-webpack-plugin 開啟緩存
- 使用 cache-loader 或者 hard-source-webpack-plugin
注意:thread-loader 和 cache-loader 兩個(gè)要一起使用的話,請(qǐng)先放 cache-loader 接著是 thread-loader 最後才是 heavy-loader
- DLL:
- 使用 DllPlugin 進(jìn)行分包,使用 DllReferencePlugin(索引鏈接) 對(duì) manifest.json 引用,讓一些基本不會(huì)改動(dòng)的代碼先打包成靜態(tài)資源,避免反復(fù)編譯浪費(fèi)時(shí)間。
2)使用webpack4-優(yōu)化原因
- (a)V8帶來的優(yōu)化(for of替代forEach、Map和Set替代Object、includes替代indexOf)
- (b)默認(rèn)使用更快的md4 hash算法
- (c)webpacks AST可以直接從loader傳遞給AST,減少解析時(shí)間
- (d)使用字符串方法替代正則表達(dá)式
①noParse
- 不去解析某個(gè)庫內(nèi)部的依賴關(guān)系
- 比如jquery 這個(gè)庫是獨(dú)立的, 則不去解析這個(gè)庫內(nèi)部依賴的其他的東西
- 在獨(dú)立庫的時(shí)候可以使用
module.exports = {
module: {
noParse: /jquery/,
rules:[]
}
}
復(fù)制代碼
②IgnorePlugin
- 忽略掉某些內(nèi)容 不去解析依賴庫內(nèi)部引用的某些內(nèi)容
- 從moment中引用 ./locol 則忽略掉
- 如果要用local的話 則必須在項(xiàng)目中必須手動(dòng)引入
import 'moment/locale/zh-cn'
module.exports = {
plugins: [
new Webpack.IgnorePlugin(/./local/, /moment/),
]
}
復(fù)制代碼
③dillPlugin
- 不會(huì)多次打包, 優(yōu)化打包時(shí)間
- 先把依賴的不變的庫打包
- 生成 manifest.json文件
- 然后在webpack.config中引入
- webpack.DllPlugin Webpack.DllReferencePlugin
④happypack -> thread-loader
- 大項(xiàng)目的時(shí)候開啟多線程打包
- 影響前端發(fā)布速度的有兩個(gè)方面,一個(gè)是構(gòu)建,一個(gè)就是壓縮,把這兩個(gè)東西優(yōu)化起來,可以減少很多發(fā)布的時(shí)間。
⑤thread-loader
thread-loader 會(huì)將您的 loader 放置在一個(gè) worker 池里面運(yùn)行,以達(dá)到多線程構(gòu)建。
把這個(gè) loader 放置在其他 loader 之前(如下圖 example 的位置), 放置在這個(gè) loader 之后的 loader 就會(huì)在一個(gè)單獨(dú)的 worker 池(worker pool)中運(yùn)行。
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /.js$/,
include: path.resolve('src'),
use: [
'thread-loader',
// 你的高開銷的loader放置在此 (e.g babel-loader)
]
}
]
}
}
復(fù)制代碼
每個(gè) worker 都是一個(gè)單獨(dú)的有 600ms 限制的 node.js 進(jìn)程。同時(shí)跨進(jìn)程的數(shù)據(jù)交換也會(huì)被限制。請(qǐng)?jiān)诟唛_銷的loader中使用,否則效果不佳
⑥壓縮加速——開啟多線程壓縮
- 不推薦使用 webpack-paralle-uglify-plugin,項(xiàng)目基本處于沒人維護(hù)的階段,issue 沒人處理,pr沒人合并。
Webpack 4.0以前:uglifyjs-webpack-plugin,parallel參數(shù)
module.exports = {
optimization: {
minimizer: [
new UglifyJsPlugin({
parallel: true,
}),
],
},};
復(fù)制代碼
- 推薦使用 terser-webpack-plugin
module.exports = {
optimization: {
minimizer: [new TerserPlugin(
parallel: true // 多線程
)],
},
};
復(fù)制代碼
2)優(yōu)化 Webpack 的打包體積
- 壓縮代碼
- 提取頁面公共資源:
- Tree shaking
- Scope hoisting
- 圖片壓縮
- 動(dòng)態(tài)Polyfill
3)speed-measure-webpack-plugin
簡稱 SMP,分析出 Webpack 打包過程中 Loader 和 Plugin 的耗時(shí),有助于找到構(gòu)建過程中的性能瓶頸。
開發(fā)階段
開啟多核壓縮
插件:** terser-webpack-plugin **
const TerserPlugin = require('terser-webpack-plugin')
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
parallel: true,
terserOptions: {
ecma: 6,
},
}),
]
}
}
復(fù)制代碼
傳送門 ?# 工程化專題
Babel
簡單描述一下 Babel 的編譯過程
Babel 是一個(gè) JavaScript 編譯器,是一個(gè)工具鏈,主要用于將采用 ECMAScript 2015+ 語法編寫的代碼轉(zhuǎn)換為向后兼容的 JavaScript 語法,以便能夠運(yùn)行在當(dāng)前和舊版本的瀏覽器或其他環(huán)境中。
Babel 本質(zhì)上就是在操作 AST 來完成代碼的轉(zhuǎn)譯。AST是抽象語法樹(Abstract Syntax Tree, AST)
如果想要了解更多,可以閱讀和嘗試:
Babel 的功能很純粹,它只是一個(gè)編譯器。大多數(shù)編譯器的工作過程可以分為三部分:
- 解析(Parse) :將源代碼轉(zhuǎn)換成更加抽象的表示方法(例如抽象語法樹)。包括詞法分析和語法分析。詞法分析主要把字符流源代碼(Char Stream)轉(zhuǎn)換成令牌流( Token Stream),語法分析主要是將令牌流轉(zhuǎn)換成抽象語法樹(Abstract Syntax Tree,AST)。
- 轉(zhuǎn)換(Transform) :通過 Babel 的插件能力,對(duì)(抽象語法樹)做一些特殊處理,將高版本語法的 AST 轉(zhuǎn)換成支持低版本語法的 AST。讓它符合編譯器的期望,當(dāng)然在此過程中也可以對(duì) AST 的 Node 節(jié)點(diǎn)進(jìn)行優(yōu)化操作,比如添加、更新以及移除節(jié)點(diǎn)等。
- 生成(Generate) :將 AST 轉(zhuǎn)換成字符串形式的低版本代碼,同時(shí)也能創(chuàng)建 Source Map 映射。
經(jīng)過這三個(gè)階段,代碼就被 Babel 轉(zhuǎn)譯成功了。
Git
Git 常用命令
查看分支:git branch
創(chuàng)建分支:git branch
切換分支:git checkout
創(chuàng)建+切換分支:git checkout -b
合并某分支到當(dāng)前分支:git merge
刪除分支:git branch -d
如何使用Git管理項(xiàng)目
實(shí)際開發(fā)中,一個(gè)倉庫(一般只放一個(gè)項(xiàng)目)主要存在兩條主分支:master與develop分支。這個(gè)兩個(gè)分支的生命周期是整個(gè)項(xiàng)目周期。
我們可能使用的不同類型的分支對(duì)項(xiàng)目進(jìn)行管理是:
-
功能分支
功能分支(或有時(shí)稱為主題分支)用于為即將發(fā)布或遙遠(yuǎn)的未來版本開發(fā)新功能。在開始開發(fā)某個(gè)功能時(shí),將包含該功能的目標(biāo)版本在那時(shí)很可能是未知的。功能分支的本質(zhì)在于,只要該功能處于開發(fā)階段,它就存在,但最終會(huì)被合并回develop (明確將新功能添加到即將發(fā)布的版本中)或丟棄。功能分支通常只存在于開發(fā)者倉庫中,而不存在于origin 。
-
發(fā)布分支
發(fā)布分支支持準(zhǔn)備新的生產(chǎn)版本。它們?cè)试S在最后一刻打點(diǎn) i 和交叉 t。此外,它們?cè)试S修復(fù)小錯(cuò)誤并為發(fā)布準(zhǔn)備元數(shù)據(jù)(版本號(hào)、構(gòu)建日期等)。通過在發(fā)布分支上完成所有這些工作,該develop 分支被清除以接收下一個(gè)大版本的功能。
- 從
develop 分支拉取,且必須合并回 develop 和 master
- 分支命名約定:
release-*
-
修補(bǔ)程序分支
Hotfix 分支與發(fā)布分支非常相似,因?yàn)樗鼈円仓荚跒樾碌纳a(chǎn)版本做準(zhǔn)備,盡管是計(jì)劃外的。它們產(chǎn)生于需要立即對(duì)現(xiàn)場(chǎng)制作版本的不良狀態(tài)采取行動(dòng)。當(dāng)必須立即解決生產(chǎn)版本中的關(guān)鍵錯(cuò)誤時(shí),可以從標(biāo)記生產(chǎn)版本的主分支上的相應(yīng)標(biāo)記中分支出一個(gè)修補(bǔ)程序分支。
master:這個(gè)分支最為穩(wěn)定,這個(gè)分支表明項(xiàng)目處于可發(fā)布的狀態(tài)。
develop:做為開發(fā)的分支,平行于master分支。
Feature branches:這種分支和咱們程序員平常開發(fā)最為密切,稱做功能分支。必須從develop分支建立,完成后合并回develop分支。
Release branches:這個(gè)分支用來分布新版本。從develop分支建立,完成后合并回develop與master分支。這個(gè)分支上能夠作一些很是小的bug修復(fù),固然,你也能夠禁止在這個(gè)分支作任何bug的修復(fù)工做,而只作版本發(fā)布的相關(guān)操做,例如設(shè)置版本號(hào)等操做,那樣的話那些發(fā)現(xiàn)的小bug就必須放到下一個(gè)版本修復(fù)了。若是在這個(gè)分支上發(fā)現(xiàn)了大bug,那么也絕對(duì)不能在這個(gè)分支上改,須要Featrue分支上改,走正常的流程。
Hotfix branches:這個(gè)分支主要為修復(fù)線上特別緊急的bug準(zhǔn)備的。必須從master分支建立,完成后合并回develop與master分支。這個(gè)分支主要是解決線上版本的緊急bug修復(fù)的,例如忽然版本V0.1上有一個(gè)致命bug,必須修復(fù)。那么咱們就能夠從master 分支上發(fā)布這個(gè)版本那個(gè)時(shí)間點(diǎn) 例如 tag v0.1(通常代碼發(fā)布后會(huì)及時(shí)在master上打tag),來建立一個(gè) hotfix-v0.1.1的分支,而后在這個(gè)分支上改bug,而后發(fā)布新的版本。最后將代碼合并回develop與master分支。
更多請(qǐng)參考
項(xiàng)目優(yōu)化
移除生產(chǎn)環(huán)境的控制臺(tái)打印 。方案很多,esling+pre-commit、使用插件自動(dòng)去除,插件包括babel-plugin-transform-remove-console、uglifyjs-webpack-plugin、terser-webpack-plugin。最后選擇了terser-webpack-plugin,腳手架vue-cli用這個(gè)插件來開啟緩存和多線程打包,無需安裝額外的插件,僅需在configureWebpack中設(shè)置terser插件的drop_console為true即可。最好還是養(yǎng)成良好的代碼習(xí)慣,在開發(fā)基本完成后去掉無用的console,vscode中的turbo console就蠻好的。
第三方庫的按需加載 。echarts,官方文檔里是使用配置文件指定使用的模塊,另一種使用babel-plugin-equire實(shí)現(xiàn)按需加載。element-ui使用babel-plugin-component實(shí)現(xiàn)按需引入。
前后端數(shù)據(jù)交換方面,推動(dòng)項(xiàng)目組使用藍(lán)湖、接口文檔,與后端同學(xué)協(xié)商,規(guī)范后臺(tái)數(shù)據(jù)返回。
雅虎軍規(guī)提到的,避免css表達(dá)式、濾鏡,較少DOM操作,優(yōu)化圖片、精靈圖,避免圖片空鏈接等 。
性能問題:頁面加載性能、動(dòng)畫性能、操作性能 。Performance API,記錄性能數(shù)據(jù)。
winter重學(xué)前端 優(yōu)化技術(shù)方案:
緩存:客戶端控制的強(qiáng)緩存策略 。
降低請(qǐng)求成本 :DNS 由客戶端控制,隔一段時(shí)間主動(dòng)請(qǐng)求獲取域名IP,不走系統(tǒng)DNS(完全看不懂)。TCP/TLS連接復(fù)用,服務(wù)器升級(jí)到HTTP2,盡量合并域名。
減少請(qǐng)求數(shù) :JS、CSS打包到HTML。JS控制圖片異步加載、懶加載。小型圖片使用data-uri。
較少傳輸體積 :盡量使用SVG\gradient代替圖片。根據(jù)機(jī)型和網(wǎng)絡(luò)狀況控制圖片清晰度。對(duì)低清晰度圖片使用銳化來提升體驗(yàn)。設(shè)計(jì)上避免大型背景圖。
使用CDN加速 ,內(nèi)容分發(fā)網(wǎng)絡(luò),是建立再承載網(wǎng)基礎(chǔ)上的虛擬分布式網(wǎng)絡(luò),能夠?qū)⒃凑緝?nèi)容緩存到全國或全球的節(jié)點(diǎn)服務(wù)器上。用戶就近獲取內(nèi)容,提高了資源的訪問速度,分擔(dān)源站壓力。
|