還需要完善的點
TCP/IP五層模型的協(xié)議 OSI七層模 -> 應用層下面有表示層和會話層
應用層 // http ftp dns
傳輸層 // tcp udp
網(wǎng)絡層 // ip
數(shù)據(jù)鏈路層 // ppp arp
物理層 // 不了解
http https
Http是基于TCP/IP協(xié)議的應用程序協(xié)議,不包括數(shù)據(jù)包的傳輸,主要規(guī)定了客戶端和服務器的通信格式,默認使用80端口
請求頭和響應頭部分字段:
Host:指定服務器域名英妓,可用來區(qū)分訪問一個服務器上的不同服務
Connection:keep-alive表示要求服務器不要關(guān)閉TCP連接,close表示明確要求關(guān)閉連接霎肯,默認值是keep-alive
Accept-Encoding:說明自己可以接收的壓縮方式
User-Agent:用戶代理趾痘,是服務器能識別客戶端的操作系統(tǒng)(Android慢哈、IOS、WEB)及相關(guān)的信息永票。作用是幫助服務器區(qū)分客戶端卵贱,并且針對不同客戶端讓用戶看到不同數(shù)據(jù),做不同操作侣集。
Content-Type:服務器告訴客戶端數(shù)據(jù)的格式键俱,常見的值有text/plain,image/jpeg世分,image/png编振,video/mp4,application/json臭埋,application/zip踪央。這些數(shù)據(jù)類型總稱為MIME TYPE。
Content-Encoding:服務器數(shù)據(jù)壓縮方式
Transfer-Encoding:chunked表示采用分塊傳輸編碼瓢阴,有該字段則無需使用Content-Length字段畅蹂。
Content-Length:聲明數(shù)據(jù)的長度,請求和回應頭部都可以使用該字段荣恐。
Cache-Control:緩存方式
Https協(xié)議是以安全為目標的Http通道魁莉,簡單來說就是Http的安全版。主要是在Http下加入SSL層(現(xiàn)在主流的是SSL/TLS),SSL是Https協(xié)議的安全基礎旗唁。Https默認端口號為443。
SSL/TLS協(xié)議基本思路是采用公鑰加密法(最有名的是RSA加密算法)痹束。大概流程是检疫,客戶端向服務器索要公鑰,然后用公鑰加密信息祷嘶,服務器收到密文屎媳,用自己的私鑰解密。
為了防止公鑰被篡改论巍,把公鑰放在數(shù)字證書中烛谊,證書可信則公鑰可信。公鑰加密計算量很大嘉汰,為了提高效率丹禀,服務端和客戶端都生成對話秘鑰,用它加密信息鞋怀,而對話秘鑰是對稱加密双泪,速度非常快密似。而公鑰用來機密對話秘鑰焙矛。
流程:
客戶端給出協(xié)議版本號、一個客戶端隨機數(shù)A(Client random)以及客戶端支持的加密方式
服務端確認雙方使用的加密方式残腌,并給出數(shù)字證書村斟、一個服務器生成的隨機數(shù)B(Server random)
客戶端確認數(shù)字證書有效,生成一個新的隨機數(shù)C(Pre-master-secret)抛猫,使用證書中的公鑰對C加密蟆盹,發(fā)送給服務端
服務端使用自己的私鑰解密出C
客戶端和服務器根據(jù)約定的加密方法,使用三個隨機數(shù)ABC邑滨,生成對話秘鑰日缨,之后的通信都用這個對話秘鑰進行加密。
Http和Https的區(qū)別如下:
https協(xié)議需要到CA申請證書掖看,大多數(shù)情況下需要一定費用
Http是超文本傳輸協(xié)議匣距,信息采用明文傳輸,Https則是具有安全性SSL加密傳輸協(xié)議
Http和Https端口號不一樣哎壳,Http是80端口毅待,Https是443端口
Http連接是無狀態(tài)的,而Https采用Http+SSL構(gòu)建可進行加密傳輸归榕、身份認證的網(wǎng)絡協(xié)議尸红,更安全。
Http協(xié)議建立連接的過程比Https協(xié)議快。因為Https除了Tcp三次握手外里,還要經(jīng)過SSL握手怎爵。連接建立之后數(shù)據(jù)傳輸速度,二者無明顯區(qū)別盅蝗。
三次握手 四次揮手
第一次握手鳖链,A向B發(fā)送信息后,B收到信息墩莫。B可確認A的發(fā)信能力和B的收信能力
第二次握手芙委,B向A發(fā)消息,A收到消息狂秦。A可確認A的發(fā)信能力和收信能力灌侣,A也可確認B的收信能力和發(fā)信能力
第三次握手,A向B發(fā)送消息裂问,B接收到消息侧啼。B可確認A的收信能力和B的發(fā)信能力
ACK:響應標識,1表示響應愕秫,連接建立成功之后慨菱,所有報文段ACK的值都為1
SYN:連接標識,1表示建立連接戴甩,連接請求和連接接受報文段SYN=1符喝,其他情況都是0
FIN:關(guān)閉連接標識,1標識關(guān)閉連接甜孤,關(guān)閉請求和關(guān)閉接受報文段FIN=1协饲,其他情況都是0,跟SYN類似
seq number:序號缴川,一個隨機數(shù)X茉稠,請求報文段中會有該字段,響應報文段沒有
ack number:應答號把夸,值為請求seq+1而线,即X+1,除了連接請求和連接接受響應報文段沒有該字段恋日,其他的報文段都有該字段
第一次握手:建立連接請求膀篮。客戶端發(fā)送連接請求報文段岂膳,將SYN置為1誓竿,seq為隨機數(shù)x。然后谈截,客戶端進入SYN_SEND狀態(tài)筷屡,等待服務器確認涧偷。
第二次握手:確認連接請求。服務器收到客戶端的SYN報文段毙死,需要對該請求進行確認燎潮,設置ack=x+1(即客戶端seq+1)。同時自己也要發(fā)送SYN請求信息扼倘,即SYN置為1跟啤,seq=y。服務器將SYN和ACK信息放在一個報文段中唉锌,一并發(fā)送給客戶端,服務器進入SYN_RECV狀態(tài)竿奏。
第三次握手:客戶端收到SYN+ACK報文段袄简,將ack設置為y+1,向服務器發(fā)送ACK報文段泛啸,這個報文段發(fā)送完畢绿语,客戶端和服務券進入ESTABLISHED狀態(tài),完成Tcp三次握手候址。
第一次揮手:主機1(可以是客戶端或服務器)吕粹,設置seq和ack向主機2發(fā)送一個FIN報文段,此時主機1進入FIN_WAIT_1狀態(tài)岗仑,表示沒有數(shù)據(jù)要發(fā)送給主機2了
第二次揮手:主機2收到主機1的FIN報文段匹耕,向主機1回應一個ACK報文段,表示同意關(guān)閉請求荠雕,主機1進入FIN_WAIT_2狀態(tài)稳其。
第三次揮手:主機2向主機1發(fā)送FIN報文段,請求關(guān)閉連接炸卑,主機2進入LAST_ACK狀態(tài)既鞠。
第四次揮手:主機1收到主機2的FIN報文段,向主機2回應ACK報文段盖文,然后主機1進入TIME_WAIT狀態(tài)嘱蛋;主機2收到主機1的ACK報文段后,關(guān)閉連接五续。此時主機1等待主機2一段時間后洒敏,沒有收到回復,證明主機2已經(jīng)正常關(guān)閉返帕,主機1頁關(guān)閉連接桐玻。
tcp udp
TCP(Transmission Control Protocol,傳輸控制協(xié)議)是面向連接的協(xié)議荆萤,在收發(fā)數(shù)據(jù)前镊靴,必須和對方建立可靠的連接(三次握手)铣卡。
UDP是一個無連接的協(xié)議
在發(fā)送端槐秧,應用層將數(shù)據(jù)傳遞給傳輸層的 UDP 協(xié)議浅乔,UDP 只會給數(shù)據(jù)增加一個 UDP 頭標識下是 UDP 協(xié)議登渣,然后就傳遞給網(wǎng)絡層了
在接收端午乓,網(wǎng)絡層將數(shù)據(jù)傳遞給傳輸層背桐,UDP 只去除 IP 報文頭就傳遞給應用層护赊,不會任何拼接操作
UDP TCP
是否連接 無連接 面向連接
是否可靠 不可靠傳輸轧钓,不使用流量控制和擁塞控制 可靠傳輸瑰钮,使用流量控制和擁塞控制
連接對象個數(shù) 支持一對一殖蚕,一對多轿衔,多對一和多對多交互通信 只能是一對一通信
傳輸方式 面向報文 面向字節(jié)流
首部開銷 首部開銷小,僅8字節(jié) 首部最小20字節(jié)睦疫,最大60字節(jié)
適用場景 適用于實時應用(IP電話害驹、視頻會議、直播等) 適用于要求可靠傳輸?shù)膽酶蛴缥募鬏?/p>
常見狀態(tài)碼
1xx(臨時響應)
100 //繼續(xù) 請求者應當繼續(xù)提出請求宛官。服務器返回此代碼表示已收到請求的第一部分,正在等待其余部分瓦糕。
101 //切換協(xié)議 請求者已要求服務器切換協(xié)議底洗,服務器已確認并準備切換。
2xx(成功)
200 //成功 服務器已經(jīng)成功處理了請求咕娄。通常亥揖,這表示服務器提供了請求的網(wǎng)頁。
201 //已創(chuàng)建 請求成功并且服務器創(chuàng)建了新的資源
202 //已接受 服務器已接受請求谭胚,但尚未處理
203 //非授權(quán)信息 服務器已經(jīng)成功處理了請求徐块,但返回的信息可能來自另一來源
204 //無內(nèi)容 服務器成功處理了請求,但沒有返回任何內(nèi)容
205 //重置內(nèi)容 服務器成功處理了請求灾而,但沒有返回任何內(nèi)容
206 //部分內(nèi)容 服務器成功處理了部分GET請求
3xx(重定向)
300 //多種選擇 針對請求胡控,服務器可執(zhí)行多種操作。服務器可根據(jù)請求者(user agent)選擇一項操作旁趟,或提供操作列表供請求者選擇昼激。
301 //永久移動 請求的網(wǎng)頁已永久移動到新位置。服務器返回此響應(對GET或HEAD請求的響應)時锡搜,會自動將請求者轉(zhuǎn)到新位置橙困。
302 //臨時移動 服務器目前從不同位置的網(wǎng)頁響應請求,但請求者應繼續(xù)使用原有位置來進行以后的請求
303 //查看其它位置 請求者應當對不同的位置使用單獨的GET請求來檢索響應時耕餐,服務器返回此代碼
304 //未修改 自動上次請求后凡傅,請求的網(wǎng)頁未修改過。服務器返回此響應肠缔,不會返回網(wǎng)頁的內(nèi)容
305 //使用代理 請求者只能使用代理訪問請求的網(wǎng)頁夏跷。如果服務器返回此響應哼转,還表示請求者應使用代理
307 //臨時性重定向 服務器目前從不同位置的網(wǎng)頁響應請求,但請求者應繼續(xù)使用原有的位置來進行以后的請求
4xx(請求錯誤)
400 //錯誤請求 服務器不理解請求的語法
401 //未授權(quán) 請求要求身份驗證槽华。對于需要登錄的網(wǎng)頁壹蔓,服務器可能返回此響應
403 //禁止 服務器拒絕請求
404 //未找到 服務器找不到請求的網(wǎng)頁
405 //方法禁用 禁用請求中指定的方法
406 //不接受 無法使用請求的內(nèi)容特性響應請求的網(wǎng)頁
407 //需要代理授權(quán) 此狀態(tài)碼與401(未授權(quán))類似,但指定請求者應當授權(quán)使用代理
408 //請求超時 服務器等候請求時發(fā)生超時
409 //沖突 服務器在完成請求時發(fā)生沖突猫态。服務器必須在響應中包含有關(guān)沖突的信息佣蓉。
410 //已刪除 如果請求的資源已永久刪除,服務器就會返回此響應
411 //需要有效長度 服務器不接受不含有效內(nèi)容長度標頭字段的請求
412 //未滿足前提條件 服務器未滿足請求者在請求者設置的其中一個前提條件
413 //請求實體過大 服務器無法處理請求亲雪,因為請求實體過大勇凭,超出了服務器的處理能力
414 //請求的URI過長 請求的URI(通常為網(wǎng)址)過長,服務器無法處理
415 //不支持媒體類型 請求的格式不受請求頁面的支持
416 //請求范圍不符合要求 如果頁面無法提供請求的范圍义辕,則服務器會返回此狀態(tài)碼
417 //未滿足期望值 服務器未滿足“期望”請求標頭字段的要求
5xx(服務器錯誤)
500 //服務器內(nèi)部錯誤 服務器遇到錯誤套像,無法完成請求
501 //尚未實施 服務器不具備完成請求的功能。例如终息,服務器無法識別請求方法時可能會返回此代碼
502 //錯誤網(wǎng)關(guān) 服務器作為網(wǎng)關(guān)或代理,從上游服務器無法收到無效響應
503 //服務器不可用 服務器目前無法使用(由于超載或者停機維護)贞让。通常周崭,這只是暫時狀態(tài)
504 //網(wǎng)關(guān)超時 服務器作為網(wǎng)關(guān)代理,但是沒有及時從上游服務器收到請求
505 //HTTP版本不受支持 服務器不支持請求中所用的HTTP協(xié)議版本
http 緩存設置 Cache-Control
HTTP緩存是在HTTP請求傳輸時用到的緩存喳张,主要在服務器代碼上設置续镇,緩存位置在本地瀏覽器
強緩存
http/1.1 Cache-Control: max-age=xxxx
http1.0+ Expires: date
Cache-Control:no-store
禁止一切緩存(這個才是響應不被緩存的意思)。緩存通常會像非緩存代理服務器一樣销部,向客戶端轉(zhuǎn)發(fā)一條 no-store 響應摸航,然后刪除對象。
Cache-Control:no-cache
強制客戶端直接向服務器發(fā)送請求,也就是說每次請求都必須向服務器發(fā)送舅桩。服務器接收到請求酱虎,然后判斷資源是否變更,是則返回新內(nèi)容擂涛,否則返回304读串,未變更。
Cache-Control:max-age
首部表示的是從服務器將文檔傳來之時起撒妈,可以認為此 文檔處于新鮮狀態(tài)的秒數(shù)恢暖。
Cache-Control:pubilc/private
是否只能被單個用戶緩存
協(xié)商緩存
當瀏覽器對某個資源的請求沒有命中強緩存,就會發(fā)一個請求到服務器狰右,驗證協(xié)商緩存是否命中杰捂,如果協(xié)商緩存命中,請求響應返回的http狀態(tài)為304并且會顯示一個Not Modified的字符串
Last-Modified 表示本地文件最后修改日期棋蚌,瀏覽器會在request header加上If-Modified-Since(上次返回的Last-Modified的值)嫁佳,詢問服務器在該日期后資源是否有更新挨队,有更新的話就會將新的資源發(fā)送回來
Etag就像一個指紋,資源變化都會導致ETag變化脱拼,跟最后修改時間沒有關(guān)系瞒瘸,ETag可以保證每一個資源是唯一的
If-None-Match的header會將上次返回的Etag發(fā)送給服務器,詢問該資源的Etag是否有更新熄浓,有變動就會發(fā)送新的資源回來
ETag的優(yōu)先級比Last-Modified更高
具體為什么要用ETag情臭,主要出于下面幾種情況考慮:
一些文件也許會周期性的更改,但是他的內(nèi)容并不改變(僅僅改變的修改時間)赌蔑,這個時候我們并不希望客戶端認為這個文件被修改了俯在,而重新GET;
某些文件修改非常頻繁娃惯,比如在秒以下的時間內(nèi)進行修改跷乐,(比方說1s內(nèi)修改了N次),If-Modified-Since能檢查到的粒度是s級的趾浅,這種修改無法判斷(或者說UNIX記錄MTIME只能精確到秒)愕提;
某些服務器不能精確的得到文件的最后修改時間。
列舉三種禁止瀏覽器緩存的頭字段皿哨,并寫出響應的設置值
Expires:告訴瀏覽器把回送的資源緩存多長時間 -1或0則是不緩存 簡要:添加Expires頭能有效的利用瀏覽器的緩存能力來改善頁面的性能浅侨,能在后續(xù)的頁面中有效避免很多不必要的Http請求,WEB服務器使用Expires頭來告訴Web客戶端它可以使用一個組件的當前副本证膨,直到指定的時間為止如输。 例如:Expires:Thu,15 Apr 2010 20:00:00 GMT; 他告訴瀏覽器緩存有效性持續(xù)到2010年4月15日為止央勒,在這個時間之內(nèi)相同的請求使用緩存不见,這個時間之外使用http請求。
Cache-Control:no-cache Cathe-Control:max-age=315360000
Expires有一個非常大的缺陷崔步,它使用一個固定的時間稳吮,要求服務器與客戶端的時鐘保持嚴格的同步,并且這一天到來后井濒,服務器還得重新設定新的時間盖高。HTTP1.1引入了Cathe-Control,它使用max-age指定組件被緩存多久眼虱,從請求開始在max-age時間內(nèi)瀏覽器使用緩存喻奥,之外的使用請求,這樣就可以消除Expires的限制捏悬, 如果對瀏覽器兼容性要求很高的話撞蚕,可以兩個都使用。
Pragma:no-cache
跨域方式
瀏覽器的同源策略導致的跨域問題过牙,同源即同協(xié)議甥厦、同域名纺铭、同端口。
主要是跨全域和 iframe 跨域刀疙。
1舶赔、JSONP
利用 Script 標簽不受瀏覽器同源策略影響:
首先前端先設置好回調(diào)函數(shù),并將其作為 url 的參數(shù)谦秧。
服務端接收到請求后竟纳,通過該參數(shù)獲得回調(diào)函數(shù)名,并將數(shù)據(jù)放在參數(shù)中將其返回疚鲤。
收到結(jié)果后因為是 script 標簽锥累,所以瀏覽器會當做是腳本進行運行,從而達到跨域獲取數(shù)據(jù)的目的集歇。
// script 標簽一般在 js 里生成桶略,然后插入 dom
<script>
function jsonpCallback(data) {
alert('獲得 X 數(shù)據(jù):' + data.x);
}
</script>
<script src="http://127.0.0.1:3000?callback=jsonpCallback"></script>
支持 GET 請求而不支持 POST 等其它類行的 HTTP 請求。
只支持跨域 HTTP 請求這種情況诲宇,不能解決不同域的兩個頁面或 iframe 之間進行數(shù)據(jù)通信的問題际歼。
2、CORS (Cross-origin resource sharing)
主要是后端設置 Access-Control-Allow-Origin: '域名'
簡單請求:
(1) 請求方法是以下三種方法之一:HEAD姑蓝、GET蹬挺、POST
(2) HTTP的頭信息不超出以下幾種字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:application/x-www-form-urlencoded、 multipart/form-data它掂、text/plain
瀏覽器判斷跨域為簡單請求時候,會在Request Header中添加 Origin (協(xié)議 + 域名 + 端口)字段 溯泣, 它表示我們的請求源虐秋,CORS服務端會將該字段作為跨源標志。
CORS接收到此次請求后 垃沦, 首先會判斷Origin是否在允許源(由服務端決定)范圍之內(nèi)客给,如果驗證通過,服務端會在Response Header 添加 Access-Control-Allow-Origin肢簿、Access-Control-Allow-Credentials等字段靶剑。
必須字段
Access-Control-Allow-Origin:表示服務端允許的請求源,*標識任何外域池充,多個源,分隔
可選字段
Access-Control-Allow-Credentials:false 表示是否允許發(fā)送Cookie桩引,設置為true同時,ajax請求設置withCredentials = true,瀏覽器的cookie就能發(fā)送到服務端
Access-Control-Expose-Headers:調(diào)用getResponseHeader()方法時候收夸,能從header中獲取的參數(shù)
非簡單請求:
非簡單請求是那種對服務器有特殊要求的請求坑匠,比如請求方法是PUT或DELETE,或者Content-Type字段的類型是application/json卧惜。
進行非簡單請求時候 厘灼, 瀏覽器會首先發(fā)出類型為OPTIONS的“預檢請求”夹纫,請求地址相同 ,
CORS服務端對“預檢請求”處理设凹,并對Response Header添加驗證字段舰讹,客戶端接受到預檢請求的返回值進行一次請求預判斷,驗證通過后闪朱,主請求發(fā)起月匣。
3、Server proxy 就是在 server 端轉(zhuǎn)發(fā)监透。
4桶错、location.hash + iframe 利用 hash 值來傳遞數(shù)據(jù)
iframe 修改 parent.location.hash 從而傳遞數(shù)據(jù)
5、window.name + iframe 利用 window.name 是個特殊的全局變量
6胀蛮、postMessage
創(chuàng)建一個 iframe院刁,使用 iframe 的一個方法 postMessage 可以向 http://localhost:8081/b.html 發(fā)送消息,然后監(jiān)聽 message粪狼,可以獲得其他文檔發(fā)來的消息退腥。
// a.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>a.html</title>
</head>
<body>
<iframe src="http://localhost:8081/b.html" style='display: none;'></iframe>
<script>
window.onload = function() {
let targetOrigin = 'http://localhost:8081';
window.frames[0].postMessage('我要給你發(fā)消息了!', targetOrigin);
}
window.addEventListener('message', function(e) {
console.log('a.html 接收到的消息:', e.data);
});
</script>
</body>
</html>
// b.html
<script>
window.addEventListener('message', function(e) {
if(e.source != window.parent) {
return;
}
let data = e.data;
console.log('b.html 接收到的消息:', data);
parent.postMessage('我已經(jīng)接收到消息了!', e.origin);
});
</script>
7、document.domain 主域相同而子域不同
XSS與CSRF分別是什么再榄,兩者有什么聯(lián)系狡刘?如何防御?
XSS即 Cross Site Script困鸥,網(wǎng)站上注入惡意的客戶端代碼嗅蔬,通過惡意腳本對客戶端網(wǎng)頁進行篡改,從而在用戶瀏覽網(wǎng)頁時疾就,對用戶瀏覽器進行控制或者獲取用戶隱私數(shù)據(jù)的一種攻擊方式澜术。
XSS攻擊可以分為3類:反射型(非持久型)、存儲型(持久型)猬腰、基于DOM鸟废。
反射型 XSS 只是簡單地把用戶輸入的數(shù)據(jù) “反射” 給瀏覽器,這種攻擊方式往往需要攻擊者誘使用戶點擊一個惡意鏈接姑荷,或者提交一個表單盒延,或者進入一個惡意網(wǎng)站時,注入腳本進入被攻擊者的網(wǎng)站鼠冕。
存儲型 XSS 會把用戶輸入的數(shù)據(jù) "存儲" 在服務器端添寺,當瀏覽器請求數(shù)據(jù)時,腳本從服務器上傳回并執(zhí)行懈费。這種 XSS 攻擊具有很強的穩(wěn)定性畦贸。
基于 DOM 的 XSS 攻擊是指通過惡意腳本修改頁面的 DOM 結(jié)構(gòu),是純粹發(fā)生在客戶端的攻擊。
XSS 攻擊的防范
現(xiàn)在主流的瀏覽器內(nèi)置了防范 XSS 的措施薄坏,例如 CSP趋厉。
HttpOnly 防止劫取 Cookie, 瀏覽器將禁止頁面的Javascript 訪問帶有 HttpOnly 屬性的Cookie。
輸入檢查 輸出檢查胶坠,對用戶輸入所包含的特殊字符或標簽進行編碼或過濾君账,如 <,>沈善,script
CSRF乡数,即 Cross Site Request Forgery,中譯是跨站請求偽造闻牡,是一種劫持受信任用戶向服務器發(fā)送非預期請求的攻擊方式净赴。
通常情況下,CSRF 攻擊是攻擊者借助受害者的 Cookie 騙取服務器的信任罩润,可以在受害者毫不知情的情況下以受害者名義偽造請求發(fā)送給受攻擊服務器玖翅,從而在并未授權(quán)的情況下執(zhí)行在權(quán)限保護之下的操作。
CSRF 攻擊的防范
驗證碼
Referer Check割以,根據(jù) HTTP 協(xié)議金度,在 HTTP 頭中有一個字段叫 Referer,它記錄了該 HTTP 請求的來源地址严沥。通過 Referer Check猜极,可以檢查請求是否來自合法的"源"。
添加 token 驗證消玄,在 HTTP 請求中以參數(shù)的形式加入一個隨機產(chǎn)生的 token跟伏,并在服務器端建立一個攔截器來驗證這個 token,如果請求中沒有 token 或者 token 內(nèi)容不正確翩瓜,則認為可能是 CSRF 攻擊而拒絕該請求受扳。
瀏覽器渲染機制 回流重繪
渲染引擎——webkit和Gecko
處理 HTML 標記并構(gòu)建 DOM 樹。
處理 CSS 標記并構(gòu)建 CSSOM 樹奥溺。
將 DOM 與 CSSOM 合并成一個渲染樹。
根據(jù)渲染樹來布局骨宠,以計算每個節(jié)點的幾何信息浮定。
將各個節(jié)點繪制到屏幕上。
渲染阻塞當瀏覽器遇到一個 script 標記時层亿,DOM 構(gòu)建將暫停桦卒,直至腳本完成執(zhí)行,然后繼續(xù)構(gòu)建DOM匿又。每次去執(zhí)行JavaScript腳本都會嚴重地阻塞DOM樹的構(gòu)建方灾,如果JavaScript腳本還操作了CSSOM,而正好這個CSSOM還沒有下載和構(gòu)建,瀏覽器甚至會延遲腳本執(zhí)行和構(gòu)建DOM裕偿,直至完成其CSSOM的下載和構(gòu)建洞慎。
replaint:屏幕的一部分重畫,不影響整體布局嘿棘,比如某個CSS的背景色變了劲腿,但元素的幾何尺寸和位置不變。
reflow: 意味著元件的幾何尺寸變了鸟妙,我們需要重新驗證并計算渲染樹焦人。是渲染樹的一部分或全部發(fā)生了變化。這就是Reflow重父,或是Layout花椭。
防抖節(jié)流的實現(xiàn)
debounce 防抖,把連續(xù)觸發(fā)的事件合并一次執(zhí)行房午,一定時間內(nèi)只出發(fā)一次矿辽,如果指定時間內(nèi)又觸發(fā)一次則重新計時。
function debounce(fn) {
let timeout = null;
return function() {
clearTimeout(timeout);
timeout = setTimeout(() => {
fn.apply(this, arguments); // 把 this 指向調(diào)用者
}, 500);
}
}
throttle 節(jié)流歪沃,一段時間只能執(zhí)行一次嗦锐。
function throttle(fn) {
let running = false;
return function() {
if (running) return;
running = true;
setTimeout(() => {
fn.apply(this, arguments);
running = false;
}, 500);
}
}
undersocre 的實現(xiàn)
_.debounce = function(func, wait, immediate) {
var timeout, result;
var later = function(context, args) {
timeout = null;
if (args) result = func.apply(context, args);
};
var debounced = restArguments(function(args) {
if (timeout) clearTimeout(timeout);
if (immediate) {
var callNow = !timeout;
timeout = setTimeout(later, wait);
if (callNow) result = func.apply(this, args);
} else {
timeout = _.delay(later, wait, this, args);
}
return result;
});
debounced.cancel = function() {
clearTimeout(timeout);
timeout = null;
};
return debounced;
};
_.throttle = function(func, wait, options) {
var timeout, context, args, result;
var previous = 0;
if (!options) options = {};
var later = function() {
previous = options.leading === false ? 0 : _.now();
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
var throttled = function() {
var now = _.now();
if (!previous && options.leading === false) previous = now;
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
throttled.cancel = function() {
clearTimeout(timeout);
previous = 0;
timeout = context = args = null;
};
return throttled;
};
簡述document和window兩個對象區(qū)別
文檔和窗口的區(qū)別
window.document === document
window是BOM中的一個對象。window是BOM的頂層對象沪曙,其他的BOM對象都是window的屬性
window 是窗口還包括 history奕污、location、navigator液走、screen 等
document就是整個DOM樹的根節(jié)點碳默。可以通過document訪問到dom樹的所有節(jié)點缘眶。
深拷貝
(function($) {
"use strict";
const types =
"Array,Object,String,Date,RegExp,Function,Boolean,Number,Null,Undefined".split(",");
for (let i = types.length; i--; ) {
$["is" + types[i]] = str => Object.prototype.toString.call(str).slice(8, -1) === types[i];
}
return $;
})(window.$ || (window.$ = {}));
function deepCopy (obj) {
const copyed = []; // 用于處理循環(huán)引用
for(let i= 0 ;i < copyed.length; i++){
if(copyed[i].current === obj){
return copyed[i].copy;
}
}
if ($.isFunction(obj)) {
return new Function("return " + obj.toString())();
}
if (obj === null || typeof obj !== "object"){
return obj;
}
let name, value;
const target = $.isArray(obj) ? [] : {};
for (name in obj) {
value = obj[name];
if ($.isArray(value) || $.isObject(value)) {
target[name] = deepCopy(value);
} else if ($.isFunction(value)) {
target[name] = new Function("return " + value.toString())();
} else {
target[name] = value;
}
}
copyed.push({current: obj, copy: target})
return target;
}
apply call bind
func.bind(thisArg[, arg1[, arg2[, ...]]])
func.call(thisArg, arg1, arg2, ...)
func.apply(thisArg, [arg1, arg2, ...])
// 需要注意的是 bind 的多次傳值和構(gòu)造函數(shù)調(diào)用時的結(jié)果
Function.prototype.myBind = function(context) {
const self = this;
const ctx = context || window;
const args = [...arguments].slice(1);
const bound = function() {
var newArgs = [...args, ...arguments];
// 構(gòu)造函數(shù)調(diào)用走這里
if (this instanceof bound) {
// 把 bound 的 prototype 指向函數(shù)
// 我在想這樣應該也可以吧
// this.__proto__ = self.prototype;
bound.prototype = Object.create(self.prototype);
// 使用 new 的新對象來調(diào)用
const result = self.apply(this, newArgs);
// new 對象的時候如果返回值是一個對象或者函數(shù)則返回的是這個結(jié)果
if((typeof result === 'object' && result !== null) || typeof result === 'function'){
return result;
}
return this;
}
return self.apply(ctx, newArgs);
}
return bound;
}
Function.prototype.myCall = function(context) {
const ctx = context || window;
const args = [...arguments].slice(1);
ctx.fn = this; // this是指調(diào)用myCall的function
ctx.fn(...args);
delete ctx.fn;
}
Function.prototype.myApply = function(context) {
const ctx = context || window;
const args = [...arguments].slice(1)[0];
ctx.fn = this;
ctx.fn(...args);
delete ctx.fn;
}
事件模型
當我們調(diào)用一個方法的時候嘱根,js會生成一個與這個方法對應的執(zhí)行環(huán)境(context),又叫執(zhí)行上下文巷懈。這個執(zhí)行環(huán)境中存在著這個方法的私有作用域该抒,上層作用域的指向,方法的參數(shù)顶燕,這個作用域中定義的變量以及這個作用域的this對象凑保。 而當一系列方法被依次調(diào)用的時候,因為js是單線程的涌攻,同一時間只能執(zhí)行一個方法欧引,于是這些方法被排隊在一個單獨的地方。這個地方被稱為執(zhí)行棧恳谎。
當一個腳本第一次執(zhí)行的時候芝此,js引擎會解析這段代碼憋肖,并將其中的同步代碼按照執(zhí)行順序加入執(zhí)行棧中,然后從頭開始執(zhí)行婚苹。如果當前執(zhí)行的是一個方法岸更,那么js會向執(zhí)行棧中添加這個方法的執(zhí)行環(huán)境,然后進入這個執(zhí)行環(huán)境繼續(xù)執(zhí)行其中的代碼租副。當這個執(zhí)行環(huán)境中的代碼 執(zhí)行完畢并返回結(jié)果后坐慰,js會退出這個執(zhí)行環(huán)境并把這個執(zhí)行環(huán)境銷毀,回到上一個方法的執(zhí)行環(huán)境用僧。结胀。這個過程反復進行,直到執(zhí)行棧中的代碼全部執(zhí)行完畢
js引擎遇到一個異步事件后并不會一直等待其返回結(jié)果责循,而是會將這個事件掛起糟港,繼續(xù)執(zhí)行執(zhí)行棧中的其他任務。當一個異步事件返回結(jié)果后院仿,js會將這個事件加入與當前執(zhí)行棧不同的另一個隊列秸抚,我們稱之為事件隊列。被放入事件隊列不會立刻執(zhí)行其回調(diào)歹垫,而是等待當前執(zhí)行棧中的所有任務都執(zhí)行完畢剥汤, 主線程處于閑置狀態(tài)時,主線程會去查找事件隊列是否有任務排惨。如果有吭敢,那么主線程會從中取出排在第一位的事件,并把這個事件對應的回調(diào)放入執(zhí)行棧中暮芭,然后執(zhí)行其中的同步代碼...鹿驼,如此反復,這樣就形成了一個無限的循環(huán)辕宏。
當執(zhí)行棧的任務執(zhí)行完了之后畜晰,會先去清空微任務隊列,再從宏任務隊列里獲取事件加入執(zhí)行棧執(zhí)行瑞筐。
以下事件屬于宏任務:
setInterval()
setTimeout()
以下事件屬于微任務
new Promise()
new MutaionObserver()
給出如下虛擬dom的數(shù)據(jù)結(jié)構(gòu)凄鼻,如何實現(xiàn)簡單的虛擬dom,渲染到目標dom樹
let demoNode = ({
tagName: 'ul',
props: {'class': 'list'},
children: [
({tagName: 'li', children: ['douyin']}),
({tagName: 'li', children: ['toutiao']})
]
});
function Element({tagName, props, children}){
if(!(this instanceof Element)){
return new Element({tagName, props, children})
}
this.tagName = tagName;
this.props = props || {};
this.children = children || [];
}
Element.prototype.render = function(){
var el = document.createElement(this.tagName),
props = this.props,
propName,
propValue;
for(propName in props){
propValue = props[propName];
el.setAttribute(propName, propValue);
}
this.children.forEach(function(child){
var childEl = null;
if(child instanceof Element){
childEl = child.render();
}else{
childEl = document.createTextNode(child);
}
el.appendChild(childEl);
});
return el;
};
bfc 兩欄布局 三欄布局 垂直水平居中實現(xiàn) 自適應的正方形
塊格式化上下文(Block Formatting Context聚假,BFC) 是Web頁面的可視化CSS渲染的一部分块蚌,是塊盒子的布局過程發(fā)生的區(qū)域,也是浮動元素與其他元素交互的區(qū)域魔策。
觸發(fā) bfc:
【1】根元素匈子,即HTML元素
【2】float的值不為none
【3】overflow的值不為visible
【4】display的值為inline-block河胎、table-cell闯袒、table-caption
【5】position的值為absolute或fixed
1.自適應兩欄布局 // bfc的區(qū)域不會與float 重疊,因此會根據(jù)父元素的寬度,和float元素的寬度政敢,自適應寬度其徙。
2.可以阻止元素被浮動元素覆蓋 // bfc 元素不會被浮動元素覆蓋
3.可以包含浮動元素——清除內(nèi)部浮動 // 計算 bfc 元素高度的時候到逊,浮動元素也參與計算噪服。
4.分屬于不同的BFC時可以阻止margin重疊
兩欄布局即左邊固定右邊自適應
<div class="side"></div>
<div class="bfc"></div>
<style>
.aside {
float: left;
width: 100px;
height: 150px;
background: #f66;
}
.bfc {
height: 200px;
background: #fcc;
overflow: hidden;
}
</style>
圣杯布局和雙飛翼布局都是三欄布局的方法,左右定寬中間自適應
圣杯布局
首先把left拔恰、middle褪尝、right都放出來
給它們?nèi)齻€設置上float: left, 脫離文檔流闹获;
一定記得給container設置上overflow: hidden; 可以形成BFC撐開文檔
left、right設置上各自的寬度
middle設置width: 100%;
給left河哑、middle避诽、right設置position: relative;
left設置 left: -leftWidth, right設置 right: -rightWidth;
container設置padding: 0, rightWidth, 0, leftWidth
<div class="content">
<div class="middle"></div>
<div class="left"></div>
<div class="right"></div>
</div>
.content {
overflow: hidden;
padding: 0 100px;
}
.middle {
position:relative;
width: 100%;
float: left;
height: 80px;
background: green;
}
.left {
position:relative;
width: 100px;
float: left;
left: -100px;
height: 80px;
margin-left: -100%;
background: yellow;
}
.right {
position: relative;
width: 100px;
float: left;
height: 80px;
margin-left: -100px;
right: -100px;
background: pink
}
首先把left、middle璃谨、right都放出來, middle中增加 mid
給它們?nèi)齻€設置上float: left, 脫離文檔流沙庐;
一定記得給container設置上overflow: hidden; 可以形成BFC撐開文檔
left、right設置上各自的寬度
middle設置width: 100%, mid 通過 margin 來撐到 100% 寬度佳吞,然后 left right 通過 - margin 放到預想的位置;
<div class="content">
<div class="middle">
<div class="mid"></div>
</div>
<div class="left"></div>
<div class="right"></div>
</div>
.content {
overflow: hidden;
}
.middle {
width: 100%;
float: left;
}
.mid {
margin: 0 100px;
height: 80px;
background: green;
}
.left {
width: 100px;
float: left;
height: 80px;
margin-left: -100%;
background: yellow;
}
.right {
width: 100px;
float: left;
height: 80px;
margin-left: -100px;
background: pink
}
垂直水平居中實現(xiàn)
<div class="wrapper">
<div class="content"></div>
</div>
// 絕對定位+margin:auto
.wrapper {
position: relative;
}
.content {
margin: auto;
position: absolute;
left: 0; right: 0; top: 0; bottom: 0;
}
// 絕對定位+margin反向偏移
.wrapper {
position: relative;
}
.content {
margin: auto;
position: absolute;
left: 50%; top: 50%;
margin-left: - (width + padding) / 2;
margin-top: - (height + padding) / 2;
}
// 絕對定位+transform反向偏移
.wrapper {
position: relative;
}
.content {
margin: auto;
position: absolute;
left: 50%; top: 50%;
transform: translate(-50%, -50%);
}
// table
// display: inline-block;
// display: flex;
自適應的正方形
使用 vw,vh,vmin,vmax;
設置垂直方向的 padding 撐開容器;
div {
width: 100%;
height:0;
padding-bottom: 100%;
}
利用偽元素的 margin(padding)-top 撐開容器
div {
width: 100%;
overflow: hidden;
}
div:after {
content: '';
display: block;
margin-top: 100%;
}
描述一下JS 的new操作符具體做了什么并實現(xiàn)一個
// 創(chuàng)建一個新對象obj
// 把obj的__proto__指向 constructor.prototype 實現(xiàn)繼承
// 執(zhí)行構(gòu)造函數(shù)拱雏,傳遞參數(shù),改變this指向 constructor.call(obj, ...args)
function _new(){
// 這里或者用 Object.Create(Constructor);
// 這樣原型的指向就有了
const obj = {};
const Constructor = Array.prototype.shift.call(arguments);
obj.__proto__ = Constructor.prototype;
const result = Constructor.apply(obj, arguments);
return typeof result === 'object' ? result : obj;
}
實現(xiàn) String.prototype.trim
String.prototype.trim = function () {
return this.replace(/^[\s]+|[\s]+$/g, '');
};
柯里化 add(a)(b)(c)和add(a,b,c)
- 參數(shù)復用底扳;2. 提前返回铸抑;3. 延遲計算/運行。
function curry(fn) {
var length = fn.length; // 閉包引用花盐,判斷參數(shù)個數(shù)是否夠了
var args = [...arguments].slice(1) || [];
return function() {
var _args = [...args, ...arguments];
if (_args.length < length) {
return curry.call(this, fn, ..._args);
} else {
return fn.apply(this, _args);
}
}
}
精準獲得頁面元素的位置
那就是使用getBoundingClientRect()方法羡滑。它返回一個對象,其中包含了left算芯、right柒昏、top、bottom四個屬性熙揍,分別對應了該元素的左上角和右下角相對于瀏覽器窗口(viewport)左上角的距離职祷。
var X= this.getBoundingClientRect().left;
var Y =this.getBoundingClientRect().top;
//再加上滾動距離,就可以得到絕對位置
var X= this.getBoundingClientRect().left+document.documentElement.scrollLeft;
var Y =this.getBoundingClientRect().top+document.documentElement.scrollTop;
proxy
var man = {
name:'jscoder',
age:22
};
var proxy = new Proxy(man, {
get: function(target, property) {
if(property in target) {
return target[property];
} else {
throw new ReferenceError(Property ${property} does not exist.
);
}
},
set: function(obj, prop, value) {}
});
cdn gzip 怎么配置
全稱Content Delivery Network即內(nèi)容分發(fā)網(wǎng)絡届囚。
①有梆、當用戶點擊APP上的內(nèi)容,APP會根據(jù)URL地址去本地DNS(域名解析系統(tǒng))尋求IP地址解析意系。
②泥耀、本地DNS系統(tǒng)會將域名的解析權(quán)交給CDN專用DNS服務器。
③蛔添、CDN專用DNS服務器痰催,將CDN的全局負載均衡設備IP地址返回用戶兜辞。
④、用戶向CDN的負載均衡設備發(fā)起內(nèi)容URL訪問請求夸溶。
⑤逸吵、CDN負載均衡設備根據(jù)用戶IP地址,以及用戶請求的內(nèi)容URL缝裁,選擇一臺用戶所屬區(qū)域的緩存服務器扫皱。
⑥、負載均衡設備告訴用戶這臺緩存服務器的IP地址捷绑,讓用戶向所選擇的緩存服務器發(fā)起請求韩脑。
⑦、用戶向緩存服務器發(fā)起請求粹污,緩存服務器響應用戶請求扰才,將用戶所需內(nèi)容傳送到用戶終端。
⑧厕怜、如果這臺緩存服務器上并沒有用戶想要的內(nèi)容衩匣,那么這臺緩存服務器就要網(wǎng)站的源服務器請求內(nèi)容。
⑨粥航、源服務器返回內(nèi)容給緩存服務器琅捏,緩存服務器發(fā)給用戶,并根據(jù)用戶自定義的緩存策略递雀,判斷要不要把內(nèi)容緩存到緩存服務器上
cdn 獲取 cdn host柄延,webpack 打包時 output 到cdn
output: {
publicPath: `${CDN_HOST}/static/`,
path: releasePath('./public/static/'),
filename: `[name].[${HASH}].js`
}
node端很簡單,只要加上compress模塊即可缀程,代碼如下
gzip
var compression = require('compression')
var app = express();
//盡量在其他中間件前使用compression
app.use(compression());
lazy load 實現(xiàn)
主要是先不填寫 img src搜吧,用其他屬性代替,監(jiān)聽滾動事件杨凑,判斷圖片距離頂部高度和滾動高度滤奈。
window.onscroll = function () {
var bodyScrollHeight = document.documentElement.scrollTop;// body滾動高度
var windowHeight = window.innerHeight;// 視窗高度
var imgs = document.getElementsByClassName('tamp-img');
for (var i =0; i < imgs.length; i++) {
var imgHeight = imgs[i].offsetTop;// 圖片距離頂部高度
if (imgHeight < windowHeight + bodyScrollHeight - 340) {
imgs[i].src = imgs[i].getAttribute('data-src');
imgs[i].className = imgs[i].className.replace('tamp-img','');
}
}
};
IntersectionObserver接口 (從屬于Intersection Observer API) 提供了一種異步觀察目標元素與其祖先元素或頂級文檔視窗(viewport)交叉狀態(tài)的方法。
IntersectionObserver.thresholds
一個包含閾值的列表, 按升序排列, 列表中的每個閾值都是監(jiān)聽對象的交叉區(qū)域與邊界區(qū)域的比率撩满。當監(jiān)聽對象的任何閾值被越過時蜒程,都會生成一個通知(Notification)。如果構(gòu)造器未傳入值, 則默認值為0伺帘。
var observer = new IntersectionObserver(function(changes){
changes.forEach(function(index,item){
if(item.intersectionRatio > 0 && item.intersectionRatio < 1){
//target:被觀察的目標元素昭躺,是一個 DOM 節(jié)點對象
item.target.src = item.target.dataset.src;
}
});
});
function addObserver(){
var listItems = document.querySelectorAll('.img-item');
listItems.forEach(function(item){
//實例的observe方法可以指定觀察哪個DOM節(jié)點
//開始觀察 observe的參數(shù)是一個 DOM 節(jié)點對象
observer.observe(item);
});
}
addObserver();
復習一下正則
看 log 是怎么寫和收集的
封裝一個 createLogger 函數(shù),傳入 options伪嫁,在需要的時候調(diào)用 logger 的寫入 log 的方法领炫。
function createLogger(options) {
logger = bunyan.createLogger({
name: logName,
streams: getStreams({debug, logDir, logFileName, errorLogFileName}),
serializers: {
...DEFAULT_SERIALIZERS,
...serializers
},
...bunyanOptions
});
return logger
}
logger.info(options);
// JSNlog 主要就是結(jié)合 logger,當js拋出異常時张咳,可以通過 setOptions 向制定 url 發(fā)送請求
// 在 server 端接收到固定 url 請求就調(diào)用 logger 寫入日志
JN.logger().setOptions({
defaultAjaxUrl: api.uploadV2Report
});
配置 sourceMap
output: {
publicPath: `${CDN_HOST}/v2/static/${page}/`,
path: releasePath(`./static/${page}/`),
filename: `[name].[${HASH}].js`,
...(SRC_MAP ? {} : {sourceMapFilename: '../../maps/[file].map'})
}
原型鏈
var Fn = function () {};
var fn = new Fn;
fn.__proto__ === Fn.prototype;
Fn.prototype.__proto__ === Object.prototype;
Object.prototype.__proto__ === null;
Object.__proto__ === Function.prototype //true Object created by Function
Function.__proto__ === Function.prototype // true Function created by Function
Object.__proto__ === Function.__proto__//true
Function.prototype.__proto__ === Object.prototype // true Function.prototype created by Object
Object.prototype.__proto__ === null;
//因此
Function instanceof Object //true
Object instanceof Function //true
原生 Ajax
request.readyState
0: 請求未初始化
1: 服務器連接已建立
2: 請求已接收
3: 請求處理中
4: 請求已完成帝洪,且響應已就緒
var request = new XMLHttpRequest(); // 新建XMLHttpRequest對象
request.onreadystatechange = function () { // 狀態(tài)發(fā)生變化時针史,函數(shù)被回調(diào)
if (request.readyState === 4) { // 成功完成
// 判斷響應結(jié)果:
if (request.status === 200) {
// 成功,通過responseText拿到響應的文本:
return success(request.responseText);
} else {
// 失敗碟狞,根據(jù)響應碼判斷失敗原因:
return fail(request.status);
}
} else {
// HTTP請求還在繼續(xù)...
}
}
// 發(fā)送請求:
request.open('GET', '/api/xxxxx');
request.send();
模板引擎實現(xiàn)
var TemplateEngine = function(html, options) {
var re = /<%([^%>]+)?%>/g, reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g, code = 'var r=[];\n', cursor = 0, match;
var add = function(line, js) {
js? (code += line.match(reExp) ? line + '\n' : 'r.push(' + line + ');\n') :
(code += line != '' ? 'r.push("' + line.replace(/"/g, '\\"') + '");\n' : '');
return add;
}
while(match = re.exec(html)) {
add(html.slice(cursor, match.index))(match[1], true);
cursor = match.index + match[0].length;
}
add(html.substr(cursor, html.length - cursor));
code += 'return r.join("");';
return new Function(code.replace(/[\r\t\n]/g, '')).apply(options);
}
var template =
'My skills:' +
'<%if(this.showSkills) {%>' +
'<%for(var index in this.skills) {%>' +
'<a href="#"><%this.skills[index]%></a>' +
'<%}%>' +
'<%} else {%>' +
'<p>none</p>' +
'<%}%>';
console.log(TemplateEngine(template, {
skills: ["js", "html", "css"],
showSkills: true
}));
模板引擎實現(xiàn)
我在想能不能直接用 es6 字符串模板 hack。
非常簡單的了解了 vuex vue-router 實現(xiàn)原理
Vue.use(Vuex)
install 方法拿到 Vue 實例婚陪,使用 mixin 在 beforeCreate 方法里混入 $store 的掛載族沃。
然后 new Vuex.Store({modules}),實例化 store泌参。
webpack 打包速度優(yōu)化脆淹、產(chǎn)物優(yōu)化
webpack-bundle-analyzer
速度優(yōu)化:
減小文件搜索范圍:使用 alias 讓 webpack 更快的檢索到文件路徑;
resolve: {
alias: {
// Ref: https://cn.vuejs.org/v2/guide/installation.html#獨立構(gòu)建-vs-運行時構(gòu)建
'vue$': 'vue/dist/vue.common.js',
'app-v2': srcPath('.'),
'yqg-common': srcPath('../../common')
}
}
設置 test|exclude|include 處理需要處理的目錄
cacheDirectory
cacheDirectory是loader的一個特定的選項,默認值是false沽一。指定的目錄(use: 'babel-loader?cacheDirectory=cacheLoader')將用來緩存loader的執(zhí)行結(jié)果盖溺,減少webpack構(gòu)建時Babel重新編譯過程。
Happypack:將原有的 webpack 對 loader 的執(zhí)行過程铣缠,從單一進程的形式擴展多進程模式烘嘱,從而加速代碼構(gòu)建;
產(chǎn)物優(yōu)化:
按需異步加載模塊蝗蛙,動態(tài) import
const Foo = () => import('./Foo.vue')
splitChunks: {
maxAsyncRequests: 12,
maxInitialRequests: 12,
cacheGroups: {
vue: {
chunks: 'all',
test: /\/node_modules\/vue/
},
util: {
chunks: 'all',
test: /\/node_modules\/(exif-js|lrz|moment|qr-image|underscore)/
},
vendor: {
chunks: 'all',
test: /\/node_modules\//,
priority: -10
}
}
}
復習 react rn
react 16.4.1
react-native 0.56.0
Hook 是 React 16.8 的新增特性蝇庭。它可以讓你在不編寫 class 的情況下使用 state 以及其他的 React 特性。
const [count, setCount] = useState(0);
useEffect 就是一個 Effect Hook捡硅,給函數(shù)組件增加了操作副作用的能力哮内。它跟 class 組件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的用途壮韭,只不過被合并成了一個 API北发。
useEffect(() => {
subscribe()
return () => {
unsubscribe();
};
});
JSBridge
JavaScript 調(diào)用 Native 的方式,主要有兩種:注入 API 和 攔截 URL SCHEME喷屋。
注入 API 方式的主要原理是琳拨,通過 WebView 提供的接口,向 JavaScript 的 Context(window)中注入對象或者方法屯曹,讓 JavaScript 調(diào)用時从绘,直接執(zhí)行相應的 Native 代碼邏輯,達到 JavaScript 調(diào)用 Native 的目的是牢。
攔截 URL SCHEME 的主要流程是:Web 端通過某種方式(例如 iframe.src)發(fā)送 URL Scheme 請求僵井,之后 Native 攔截到請求并根據(jù) URL SCHEME(包括所帶的參數(shù))進行相關(guān)操作
Native 調(diào)用 JavaScript,其實就是執(zhí)行拼接 JavaScript 字符串驳棱,從外部調(diào)用 JavaScript 中的方法批什,因此 JavaScript 的方法必須在全局的 window 上。(閉包里的方法社搅,JavaScript 自己都調(diào)用不了驻债,更不用想讓 Native 去調(diào)用了)
項目中使用的是 WebViewJavascriptBridge
IOS主要使用的是 攔截 URL SCHEME 的方式乳规。(看了下 android 似乎是類似的實現(xiàn))
初始化時,在 window 上建立一個 WebViewJavascriptBridge 對象合呐,并且初始化 iframe暮的。
window.WebViewJavascriptBridge = {
init: init,
send: send,
registerHandler: registerHandler,
callHandler: callHandler,
_fetchQueue: _fetchQueue,
_handleMessageFromObjC: _handleMessageFromObjC
}
var doc = document
_createQueueReadyIframe(doc)
var readyEvent = doc.createEvent('Events')
readyEvent.initEvent('WebViewJavascriptBridgeReady')
readyEvent.bridge = WebViewJavascriptBridge
doc.dispatchEvent(readyEvent)
function _createQueueReadyIframe(doc) {
messagingIframe = doc.createElement('iframe')
messagingIframe.style.display = 'none'
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE
doc.documentElement.appendChild(messagingIframe)
}
然后調(diào)用 WebViewJavascriptBridge 的 init 方法,調(diào)用時使用 callHandler 方法淌实,他會調(diào)用 _doSend 方法冻辩,如果有回調(diào)的話,會生成一個 callbackId拆祈,把 callback 加入 responseCallbacks 中恨闪,并把 callbackId 添加到發(fā)送給 native 的 message 中, 然后把 message 添加到 sendMessageQueue 中, 設置 iframe src放坏,發(fā)送請求咙咽。
function callHandler(handlerName, data, responseCallback) {
_doSend({ handlerName:handlerName, data:data }, responseCallback)
}
function _doSend(message, responseCallback) {
if (responseCallback) {
var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime()
responseCallbacks[callbackId] = responseCallback
message['callbackId'] = callbackId
}
sendMessageQueue.push(message)
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE
}
native 攔截到這個 SCHEME 后,會調(diào)用 WebViewJavascriptBridge._fetchQueue(注意 android 這里不能直接拿到返回值淤年,所以把返回值拼到 SCHEME 后面钧敞,相當于再發(fā)一次請求),其實就是將 sendMessageQueue JSON.stringify麸粮,拿到這個字符串去解析犁享,native 解析到 callbackId 就把 callbackId 轉(zhuǎn)成 responseId 向 js 發(fā)一個請求(我感覺像是往 receiveMessageQueue 里塞了個回調(diào)函數(shù),然后觸發(fā)了 _doDispatchMessageFromObjC)豹休。
_doDispatchMessageFromObjC 有兩種情況炊昆,一種是回調(diào),一種是 native 主動調(diào)用
function _handleMessageFromObjC(messageJSON) {
if (receiveMessageQueue) {
receiveMessageQueue.push(messageJSON)
} else {
_dispatchMessageFromObjC(messageJSON)
}
}
function _doDispatchMessageFromObjC() {
var message = JSON.parse(messageJSON);
var messageHandler;
var responseCallback;
//回調(diào)
if (message.responseId) {
responseCallback = responseCallbacks[message.responseId];
if (!responseCallback) {
return;
}
responseCallback(message.responseData);
delete responseCallbacks[message.responseId];
} else {//主動調(diào)用
if (message.callbackId) {
var callbackResponseId = message.callbackId;
responseCallback = function(responseData) {
_doSend({ handlerName: message.handlerName, responseId: callbackResponseId, responseData: responseData });
};
}
//獲取JS注冊的函數(shù)
var handler = messageHandlers[message.handlerName];
if (!handler) {
console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message);
} else {
//調(diào)用JS中的對應函數(shù)處理
handler(message.data, responseCallback);
}
}
}
react native
setup props and state
componentWillMount
render
componentDidMount
props:
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
render
componentDidUpdate
state:
shouldComponentUpdate
componentWillUpdate
render
componentDidUpdate
componentWillUnmount
promise 實現(xiàn)
const PROMISE_STATUS = {
pending: 'pending',
fulfilled: 'fulfilled',
rejected: 'rejected'
}
function MyPromise(fn) {
const self = this;
// 初始化狀態(tài)威根,回調(diào)隊列及結(jié)果(這個結(jié)果可以用于透傳)
self.status = PROMISE_STATUS.pending;
self.onResolvedCallback = [];
self.onRejectedCallBack = [];
self.data = null;
function resolve(value) {
self.data = value;
self.status = PROMISE_STATUS.fulfilled;
// 如果回調(diào)隊列有值凤巨,說明調(diào)用 then 時異步任務還未完成,所以 then 時把
// 回調(diào)加入回調(diào)隊列洛搀,當異步任務結(jié)束時敢茁,執(zhí)行回調(diào)隊列的任務,要變成異步任務留美。
if (self.onResolvedCallback.length) {
setTimeout(() => {
for (callback of self.onResolvedCallback) {
callback(value);
}
}, 0);
}
}
function reject(error) {
self.data = error;
self.status = PROMISE_STATUS.rejected;
if (this.onRejectedCallBack.length) {
setTimeout(() => {
for (callback of this.onRejectedCallBack) {
callback(reason);
}
}, 0);
}
}
try {
fn(resolve, reject);
} catch(err) {
reject(err);
}
}
MyPromise.prototype.then = function(onFulfilled, onRejected) {
const self = this;
// then 的返回值是一個 promise彰檬,當前 promise 當返回值還是 promise 實例時,
// 調(diào)用實例的 then 方法谎砾,把 resolve 和 reject 當作 onFulfilled, onRejected逢倍,使其自執(zhí)行。
if (self.status === PROMISE_STATUS.fulfilled) {
return new MyPromise((resolve, reject) => {
const result = onFulfilled();
if (result instanceof MyPromise) {
// 當執(zhí)行結(jié)果返回的是一個promise實例
// 等待這個promise實例狀態(tài)改變后景图,把promise實例的結(jié)果透傳給 then 這個 promise 的 resolve较雕,
// 其實就是 resolve(result的結(jié)果)
result.then(resolve, reject);
} else {
resolve(result);
}
});
} else if (self.status === PROMISE_STATUS.rejected) {
return new MyPromise((resolve, reject) => {
const result = onRejected();
if (result instanceof MyPromise) {
result.then(resolve, reject);
} else {
reject(result);
}
});
} else {
// 當狀態(tài)還是 pending 時,說明異步任務還未執(zhí)行完,所以把回調(diào)添加到回調(diào)隊列亮蒋。
return new MyPromise((resolve, reject) => {
self.onResolvedCallback.push((value) => {
setTimeout(() => {
try {
var result = onFulfilled(self.data);
if (result instanceof Promise) {
result.then(resolve, reject);
} else {
resolve(result);
}
} catch (err) {
reject(err);
}
});
});
self.onRejectedCallBack.push((value) => {
setTimeout(function() {
try {
var result = onFulfilled(self.data);
if (result instanceof Promise) {
result.then(resolve, reject);
} else {
resolve(result);
}
} catch (err) {
reject(err);
}
});
});
})
}
}
MyPromise.prototype.catch = function(onRejected) {
return this.then(null, onRejected);
}
const p = new MyPromise(function(resolve, reject) {
setTimeout(function() {
resolve(1);
}, 2000);
});
p.then(function(v) {
console.log(v);
return 2;
}).then(function(v) {
console.log(v);
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(3);
}, 3000);
});
}).then(function(v) {
console.log(v);
});
promise.all 實現(xiàn)
1扣典、接收一個 Promise 實例的數(shù)組或具有 Iterator 接口的對象
2、如果元素不是 Promise 對象慎玖,則使用 Promise.resolve 轉(zhuǎn)成 Promise 對象
3贮尖、如果全部成功,狀態(tài)變?yōu)?resolved趁怔,返回值將組成一個數(shù)組傳給回調(diào)4湿硝、只要有一個失敗,狀態(tài)就變?yōu)?rejected痕钢,返回值將直接傳遞給回調(diào)all() 的返回值也是新的 Promise 對象
function promiseAll(promises) {
return new Promise(function(resolve, reject) {
if (!isArray(promises)) {
return reject(new TypeError('arguments must be an array'));
}
let resolvedCounter = 0;
const promiseNum = promises.length;
const resolvedValues = new Array(promiseNum);
for (let i = 0; i < promiseNum; i++) {
Promise.resolve(promises[i]).then(function(value) {
resolvedCounter++;
resolvedValues[i] = value;
if (resolvedCounter === promiseNum) {
return resolve(resolvedValues)
}
}).catch(err => (reject(err)));
}
})
}
node event loop
外部輸入數(shù)據(jù)-->輪詢階段(poll)-->檢查階段(check)-->關(guān)閉事件回調(diào)階段(close callback)-->定時器檢測階段(timer)-->I/O事件回調(diào)階段(I/O callbacks)-->閑置階段(idle, prepare)-->輪詢階段...
timers: 這個階段執(zhí)行定時器隊列中的回調(diào)如 setTimeout() 和 setInterval()。
I/O callbacks: 這個階段執(zhí)行幾乎所有的回調(diào)序六。但是不包括close事件任连,定時器和setImmediate()的回調(diào)。
idle, prepare: 這個階段僅在內(nèi)部使用例诀,可以不必理會随抠。
poll: 等待新的I/O事件,node在一些特殊情況下會阻塞在這里繁涂。
check: setImmediate()的回調(diào)會在這個階段執(zhí)行拱她。
close callbacks: 例如socket.on('close', ...)這種close事件的回調(diào)。
poll階段
當個v8引擎將js代碼解析后傳入libuv引擎后扔罪,循環(huán)首先進入poll階段秉沼。poll階段的執(zhí)行邏輯如下: 先查看poll queue中是否有事件,有任務就按先進先出的順序依次執(zhí)行回調(diào)矿酵。 當queue為空時唬复,會檢查是否有setImmediate()的callback,如果有就進入check階段執(zhí)行這些callback全肮。但同時也會檢查是否有到期的timer敞咧,如果有,就把這些到期的timer的callback按照調(diào)用順序放到timer queue中辜腺,之后循環(huán)會進入timer階段執(zhí)行queue中的 callback休建。 這兩者的順序是不固定的,收到代碼運行的環(huán)境的影響评疗。如果兩者的queue都是空的测砂,那么loop會在poll階段停留,直到有一個i/o事件返回百匆,循環(huán)會進入i/o callback階段并立即執(zhí)行這個事件的callback邑彪。
值得注意的是,poll階段在執(zhí)行poll queue中的回調(diào)時實際上不會無限的執(zhí)行下去胧华。有兩種情況poll階段會終止執(zhí)行poll queue中的下一個回調(diào):1.所有回調(diào)執(zhí)行完畢寄症。2.執(zhí)行數(shù)超過了node的限制宙彪。
check階段
check階段專門用來執(zhí)行setImmediate()方法的回調(diào),當poll階段進入空閑狀態(tài)有巧,并且setImmediate queue中有callback時释漆,事件循環(huán)進入這個階段。
close階段
當一個socket連接或者一個handle被突然關(guān)閉時(例如調(diào)用了socket.destroy()方法)篮迎,close事件會被發(fā)送到這個階段執(zhí)行回調(diào)男图。否則事件會用process.nextTick()方法發(fā)送出去。
timer階段
這個階段以先進先出的方式執(zhí)行所有到期的timer加入timer隊列里的callback甜橱,一個timer callback指得是一個通過setTimeout或者setInterval函數(shù)設置的回調(diào)函數(shù)逊笆。
I/O callback階段
如上文所言,這個階段主要執(zhí)行大部分I/O事件的回調(diào)岂傲,包括一些為操作系統(tǒng)執(zhí)行的回調(diào)难裆。例如一個TCP連接生錯誤時,系統(tǒng)需要執(zhí)行回調(diào)來獲得這個錯誤的報告镊掖。
閑置階段(idle, prepare)
process.nextTick()
盡管沒有提及乃戈,但是實際上node中存在著一個特殊的隊列,即nextTick queue亩进。這個隊列中的回調(diào)執(zhí)行雖然沒有被表示為一個階段症虑,當時這些事件卻會在每一個階段執(zhí)行完畢準備進入下一個階段時優(yōu)先執(zhí)行。當事件循環(huán)準備進入下一個階段之前归薛,會先檢查nextTick queue中是否有任務谍憔,如果有,那么會先清空這個隊列主籍。與執(zhí)行poll queue中的任務不同的是韵卤,這個操作在隊列清空前是不會停止的。這也就意味著崇猫,錯誤的使用process.nextTick()方法會導致node進入一個死循環(huán)沈条。。直到內(nèi)存泄漏诅炉。
因為在I/O事件的回調(diào)中蜡歹,setImmediate方法的回調(diào)永遠在timer的回調(diào)前執(zhí)行。