1.瀏覽器從輸入URL到渲染完頁面的整個過程
- 瀏覽器先檢視瀏覽器快取-系統快取-路由器快取,若快取中有,請略過中間步驟,直接跳到第9步~若沒有,則按照下面的步驟進行操作。
- 瀏覽器從url中解析出伺服器的主機名,並將主機名轉換成伺服器的IP地址(DNS查詢)
- 瀏覽器建立一條與伺服器的tcp連線(建立過程:三次握手)。瀏覽器利用IP直接與網站主機通訊。瀏覽器發出TCP(SYN標誌位為1)連線請求,主機返回TCP(SYN,ACK標誌位均為1)應答報文,瀏覽器收到應答報文發現ACK標誌位為1,表示連線請求確認。瀏覽器返回TCP()確認報文,主機收到確認報文,三次握手,TCP連結建立完成。
- 瀏覽器通過tcp連線向伺服器傳送http請求,請求資料包。
- 伺服器處理HTTP請求,返回響應。
- 瀏覽器檢查HTTP響應是否為一個重定向(3XX結果狀態碼)、一個驗證請求(401)、錯誤(4XX、5XX)等等,這些都需要根據具體情況分類處理。
- 瀏覽器接收HTTP響應並且可能關掉TCP連線,或者是重新建立連線使用新情求,獲得新響應。
- 瀏覽器渲染頁面:1. 解析HTML
2. 構建DOM樹
3. DOM樹與CSS樣式進行附著構造呈現樹
4. 佈局
5. 繪製
上述這個過程是逐步完成的,為了更好的使用者體驗,渲染引擎將會盡可能早的將內容呈現到螢幕上,並不會等到所有的html都解析完成之後再去構建和佈局render樹。它是解析完一部分內容就顯示一部分內容,同時,可能還在通過網路下載其餘內容。
2.javascript的併發模型與事件迴圈
棧:函式呼叫形成了一個棧幀。
function foo(b) {
var a = 10;
return a + b + 11;
}
function bar(x) {
var y = 3;
return foo(x * y);
}
console.log(bar(7));
複製程式碼
當呼叫bar時,建立了第一個幀 ,幀中包含了bar的引數和區域性變數。當bar呼叫foo時,第二個幀就被建立,並被壓到第一個幀之上,幀中包含了foo的引數和區域性變數。當foo返回時,最上層的幀就被彈出棧(剩下bar函式的呼叫幀 )。當bar返回的時候,棧就空了。
堆:物件被分配在一個堆中,即用以表示一個大部分非結構化的記憶體區域。
佇列:一個 JavaScript 執行時包含了一個待處理的訊息佇列。每一個訊息都有一個為了處理這個訊息相關聯的函式。
在事件迴圈時,runtime (執行時)總是從最先進入佇列的一個訊息開始處理佇列中的訊息。正因如此,這個訊息就會被移出佇列,並將其作為輸入引數呼叫與之關聯的函式。為了使用這個函式,呼叫一個函式總是會為其創造一個新的棧幀( stack frame),一如既往。
函式的處理會一直進行直到執行棧再次為空;然後事件迴圈(event loop)將會處理佇列中的下一個訊息(如果還有的話)。
事件迴圈:之所以稱為事件迴圈,是因為它經常被用於類似如下的方式來實現:
while (queue.waitForMessage()) {
queue.processNextMessage();
}複製程式碼
"執行至完成"
每一個訊息完整的執行後,其它訊息才會被執行。當你分析你的程式時,這點提供了一些優秀的特性,包括每當一個函式執行時,它就不能被搶佔,並且在其他程式碼執行之前完全執行(且可以修改此函式操作的資料)。這與C語言不同,例如,如果函式線上程中執行,則可以在任何位置終止然後在另一個執行緒中執行其他程式碼。
這個模型的一個缺點在於當一個訊息需要太長時間才能完成,Web應用無法處理使用者的互動,例如點選或滾動。瀏覽器用“程式需要過長時間執行”的對話方塊來緩解這個問題。一個很好的做法是使訊息處理縮短,如果可能,將一個訊息裁剪成幾個訊息。
新增訊息
在瀏覽器裡,當一個事件出現且有一個事件監聽器被繫結時,訊息會被隨時新增。如果沒有事件監聽器,事件會丟失。所以點選一個附帶點選事件處理函式的元素會新增一個訊息。其它事件亦然。
呼叫 setTimeout
函式會在一個時間段過去後在佇列中新增一個訊息。這個時間段作為函式的第二個引數被傳入。如果佇列中沒有其它訊息,訊息會被馬上處理。但是,如果有其它訊息,setTimeout
訊息必須等待其它訊息處理完。因此第二個引數僅僅表示最少的時間 而非確切的時間。
零延遲
零延遲並不是意味著回撥會立即執行。在零延遲呼叫 setTimeout 時,其並不是過了給定的時間間隔後就馬上執行回撥函式。其等待的時間基於佇列里正在等待的訊息數量。在下面的例子中,"this is just a message" 將會在回撥 (callback) 獲得處理之前輸出到控制檯,這是因為延遲是要求執行時 (runtime) 處理請求所需的最小時間,但不是有所保證的時間。
(function() {
console.log('this is the start');
setTimeout(function cb() {
console.log('this is a msg from call back');
});
console.log('this is just a message');
setTimeout(function cb1() {
console.log('this is a msg from call back1');
}, 0);
console.log('this is the end');
})();
// "this is the start"
// "this is just a message"
// "this is the end"
// note that function return, which is undefined, happens here
// "this is a msg from call back"
// "this is a msg from call back1"
複製程式碼
多個執行時互相通訊
一個 web worker 或者一個跨域的iframe都有自己的棧,堆和訊息佇列。兩個不同的執行時只能通過 postMessage方法進行通訊。如果後者偵聽到message事件,則此方法會向其他執行時新增訊息。
永不阻塞
事件迴圈模型的一個非常有趣的特性是 JavaScript,與許多其他語言不同,它永不阻塞。 處理 I/O 通常通過事件和回撥來執行,所以當一個應用正等待IndexedDB查詢返回或者一個 XHR 請求返回時,它仍然可以處理其它事情,如使用者輸入。
例外是存在的,如 alert或者同步 XHR,但應該儘量避免使用它們。
上面的內容摘自MDN,看起來是不是有點官方,其實所謂的事件迴圈就是javascript的主執行緒重複從訊息佇列中取訊息、執行的過程。
3.WebSocket的底層原理
Websocket實現了瀏覽器與伺服器全雙工通訊,能更好的節省伺服器資源和頻寬並達到實時通訊的目的。它與HTTP一樣通過已建立的TCP連線來傳輸資料。websocket是一個持久化連線的協議,一個典型的websocket連線:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
複製程式碼
熟悉http協議的能夠看出來,它比http協議多了幾個東西,如下就是重點,用於告知伺服器我們客戶端使用的是websocket協議,不是那老套的http協議。
Upgrade: websocket
Connection: Upgrade
複製程式碼
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
複製程式碼
首先,Sec-WebSocket-Key 是一個Base64 encode的值,這個是瀏覽器隨機生成的,用於驗證伺服器,要求服務端必須返回一個對應加密的Sec-WebSocket-Accept應答,否則客戶端會丟擲Error during WebSocket handshake錯誤,並關閉連線。Sec_WebSocket-Protocol 是一個使用者定義的字串,用來區分同URL下,不同的服務所需要的協議。Sec-WebSocket-Version 是告訴伺服器所使用的Websocket Draft(協議版本),然後伺服器會返回下列東西,表示已經接受到請求, 成功建立Websocket,後續伺服器與瀏覽器之間的通訊不需要向http協議那樣重複傳送httpHeader。
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
複製程式碼
4.typeof運算子對各個型別的返回結果
1.undefined:undefined
2.null:object
3.string:string
4.number:number
5.boolean:boolean
6.function:function
7.object:object
8.array:object(Array:function)
9.NaN:number
5.判斷是否為陣列
1.instanceof :
var ary = [2,3];
console.log(ary instanceof Array)//true;
複製程式碼
2.原型鏈方法
var ary = [2,4];
console.log(ary.__proto__.constructor==Array);//true
console.log(ary.constructor==Array)//true 這兩段程式碼是一樣的
複製程式碼
上述兩種方法都是有侷限性的:
instanceof 和constructor 判斷的變數,必須在當前頁面宣告的,比如,一個頁面(父頁面)有一個框架,框架中引用了一個頁面(子頁面),在子頁面中宣告瞭一個arr,並將其賦值給父頁面的一個變數,這時判斷該變數,Array == arr.constructor;會返回false;原因就是Array是引用型資料,1.在傳遞過程中,僅僅是引用地址的傳遞。
2.每個頁面的Array原生物件所引用的地址是不一樣的,在子頁面宣告的array,所對應的建構函式,是子頁面的Array物件;父頁面來進行判斷,使用的Array並不等於子頁面的Array,從而造成原型鏈的斷裂。
3. Object.prototype.toString.call()
採用Object.prototype.toString.call()
算是最穩定也是通用的一個方法,Object.prototype.toString()
返回一個表示該物件的字串,每個物件都有一個toString()方法,當該物件被表示為一個文字值時,或者一個物件以預期的字串方式引用時自動呼叫。預設情況下,toString()方法被每個Object物件繼承。如果此方法在自定義物件中未被覆蓋,toString() 返回 "[object type]",其中type是物件的型別。
var arr = [2,3];
function isArray(o){
return Object.prototype.toString.call(o)=='[object Array]';
}
console.log(isArray(arr));
複製程式碼
6.實現一個柯里化函式
場景:記錄一個員工一個月的總加班時間,我們首先想到的可能會是這樣實現:
var sumTime = 0;
function overTime(time){
return sumTime += time;
}
overTime(3.0);
overTime(7);
overTime(4.5);
......
console.log(overTime());複製程式碼
逐次累加,這樣看起來一點毛病都沒有,但是當資料量很大時,就會影響效能了,那麼這時柯里化就可以解決我們的問題了。我們可以不用每次都疊加,只需要將每次加班的時間儲存起來,到最後再疊加就ok了。
var overTime = (function(){ var args = []; return function () { if(arguments.length === 0){ var time = 0; for(var i = 0;len = args.length,i<len;i++){ time += args[i]; } return time; }else{ [].push.apply(args, arguments); } }})()
overTime(3.0);overTime(4.5);overTime(7.0);console.log(overTime());//14.5複製程式碼
看到這裡你應該有點理解什麼叫函式柯里化了,柯里化(英語:Currying),又譯為卡瑞化或加里化,是把接受多個引數的函式變換成接受一個單一引數(最初函式的第一個引數)的函式,並且返回接受餘下的引數而且返回結果的新函式的技術。