實現有并行限制的 Promise 調度器
題目描述:JS 實現一個帶并發(fā)限制的異步調度器 Scheduler,保證同時運行的任務最多有兩個
addTask(1000,"1");
addTask(500,"2");
addTask(300,"3");
addTask(400,"4");
的輸出順序是:2 3 1 4
整個的完整執(zhí)行流程:
一開始1、2兩個任務開始執(zhí)行
500ms時,2任務執(zhí)行完畢,輸出2,任務3開始執(zhí)行
800ms時,3任務執(zhí)行完畢,輸出3,任務4開始執(zhí)行
1000ms時,1任務執(zhí)行完畢,輸出1,此時只剩下4任務在執(zhí)行
1200ms時,4任務執(zhí)行完畢,輸出4
實現代碼如下:
class Scheduler {
constructor(limit) {
this.queue = [];
this.maxCount = limit;
this.runCounts = 0;
}
add(time, order) {
const promiseCreator = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(order);
resolve();
}, time);
});
};
this.queue.push(promiseCreator);
}
taskStart() {
for (let i = 0; i < this.maxCount; i++) {
this.request();
}
}
request() {
if (!this.queue || !this.queue.length || this.runCounts >= this.maxCount) {
return;
}
this.runCounts++;
this.queue
.shift()()
.then(() => {
this.runCounts--;
this.request();
});
}
}
const scheduler = new Scheduler(2);
const addTask = (time, order) => {
scheduler.add(time, order);
};
addTask(1000, "1");
addTask(500, "2");
addTask(300, "3");
addTask(400, "4");
scheduler.taskStart();
什么是HTTPS協(xié)議?
超文本傳輸安全協(xié)議(Hypertext Transfer Protocol Secure,簡稱:HTTPS)是一種通過計算機網絡進行安全通信的傳輸協(xié)議。HTTPS經由HTTP進行通信,利用SSL/TLS來加密數據包。HTTPS的主要目的是提供對網站服務器的身份認證,保護交換數據的隱私與完整性。 HTTP協(xié)議采用明文傳輸信息,存在信息竊聽、信息篡改和信息劫持的風險,而協(xié)議TLS/SSL具有身份驗證、信息加密和完整性校驗的功能,可以避免此類問題發(fā)生。
安全層的主要職責就是對發(fā)起的HTTP請求的數據進行加密操作 和 對接收到的HTTP的內容進行解密操作。
Node 中的 Event Loop 和瀏覽器中的有什么區(qū)別?process.nextTick 執(zhí)行順序?
Node 中的 Event Loop 和瀏覽器中的是完全不相同的東西。
Node 的 Event Loop 分為 6 個階段,它們會按照順序反復運行。每當進入某一個階段的時候,都會從對應的回調隊列中取出函數去執(zhí)行。當隊列為空或者執(zhí)行的回調函數數量到達系統(tǒng)設定的閾值,就會進入下一階段。
(1)Timers(計時器階段):初次進入事件循環(huán),會從計時器階段開始。此階段會判斷是否存在過期的計時器回調(包含 setTimeout 和 setInterval),如果存在則會執(zhí)行所有過期的計時器回調,執(zhí)行完畢后,如果回調中觸發(fā)了相應的微任務,會接著執(zhí)行所有微任務,執(zhí)行完微任務后再進入 Pending callbacks 階段。
(2)Pending callbacks:執(zhí)行推遲到下一個循環(huán)迭代的I / O回調(系統(tǒng)調用相關的回調)。
(3)Idle/Prepare:僅供內部使用。
(4)Poll(輪詢階段):
- 當回調隊列不為空時:會執(zhí)行回調,若回調中觸發(fā)了相應的微任務,這里的微任務執(zhí)行時機和其他地方有所不同,不會等到所有回調執(zhí)行完畢后才執(zhí)行,而是針對每一個回調執(zhí)行完畢后,就執(zhí)行相應微任務。執(zhí)行完所有的回調后,變?yōu)橄旅娴那闆r。
- 當回調隊列為空時(沒有回調或所有回調執(zhí)行完畢):但如果存在有計時器(setTimeout、setInterval和setImmediate)沒有執(zhí)行,會結束輪詢階段,進入 Check 階段。否則會阻塞并等待任何正在執(zhí)行的I/O操作完成,并馬上執(zhí)行相應的回調,直到所有回調執(zhí)行完畢。
(5)Check(查詢階段):會檢查是否存在 setImmediate 相關的回調,如果存在則執(zhí)行所有回調,執(zhí)行完畢后,如果回調中觸發(fā)了相應的微任務,會接著執(zhí)行所有微任務,執(zhí)行完微任務后再進入 Close callbacks 階段。
(6)Close callbacks:執(zhí)行一些關閉回調,比如socket.on('close', ...)等。
下面來看一個例子,首先在有些情況下,定時器的執(zhí)行順序其實是隨機的
setTimeout(() => { console.log('setTimeout')}, 0)setImmediate(() => { console.log('setImmediate')})
對于以上代碼來說,setTimeout 可能執(zhí)行在前,也可能執(zhí)行在后
- 首先
setTimeout(fn, 0) === setTimeout(fn, 1),這是由源碼決定的 - 進入事件循環(huán)也是需要成本的,如果在準備時候花費了大于 1ms 的時間,那么在 timer 階段就會直接執(zhí)行
setTimeout回調 - 那么如果準備時間花費小于 1ms,那么就是
setImmediate回調先執(zhí)行了
當然在某些情況下,他們的執(zhí)行順序一定是固定的,比如以下代碼:
const fs = require('fs')
fs.readFile(__filename, () => {
setTimeout(() => {
console.log('timeout');
}, 0)
setImmediate(() => {
console.log('immediate')
})
})
在上述代碼中,setImmediate 永遠先執(zhí)行。因為兩個代碼寫在 IO 回調中,IO 回調是在 poll 階段執(zhí)行,當回調執(zhí)行完畢后隊列為空,發(fā)現存在 setImmediate 回調,所以就直接跳轉到 check 階段去執(zhí)行回調了。
上面都是 macrotask 的執(zhí)行情況,對于 microtask 來說,它會在以上每個階段完成前清空 microtask 隊列,
setTimeout(() => {
console.log('timer21')
}, 0)
Promise.resolve().then(function() {
console.log('promise1')
})
對于以上代碼來說,其實和瀏覽器中的輸出是一樣的,microtask 永遠執(zhí)行在 macrotask 前面。
最后來看 Node 中的 process.nextTick,這個函數其實是獨立于 Event Loop 之外的,它有一個自己的隊列,當每個階段完成后,如果存在 nextTick 隊列,就會清空隊列中的所有回調函數,并且優(yōu)先于其他 microtask 執(zhí)行。
setTimeout(() => {
console.log('timer1')
Promise.resolve().then(function() {
console.log('promise1')
})
}, 0)
process.nextTick(() => {
console.log('nextTick')
process.nextTick(() => {
console.log('nextTick')
process.nextTick(() => {
console.log('nextTick')
process.nextTick(() => {
console.log('nextTick')
})
})
})
})
對于以上代碼,永遠都是先把 nextTick 全部打印出來。
對瀏覽器內核的理解
瀏覽器內核主要分成兩部分:
- 渲染引擎的職責就是渲染,即在瀏覽器窗口中顯示所請求的內容。默認情況下,渲染引擎可以顯示 html、xml 文檔及圖片,它也可以借助插件顯示其他類型數據,例如使用 PDF 閱讀器插件,可以顯示 PDF 格式。
- JS 引擎:解析和執(zhí)行 javascript 來實現網頁的動態(tài)效果。
最開始渲染引擎和 JS 引擎并沒有區(qū)分的很明確,后來 JS 引擎越來越獨立,內核就傾向于只指渲染引擎。
代碼輸出結果
const promise = Promise.resolve().then(() => {
return promise;
})
promise.catch(console.err)
輸出結果如下:
Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>
這里其實是一個坑,.then 或 .catch 返回的值不能是 promise 本身,否則會造成死循環(huán)。
display的block、inline和inline-block的區(qū)別
(1)block: 會獨占一行,多個元素會另起一行,可以設置width、height、margin和padding屬性;
(2)inline: 元素不會獨占一行,設置width、height屬性無效。但可以設置水平方向的margin和padding屬性,不能設置垂直方向的padding和margin;
(3)inline-block: 將對象設置為inline對象,但對象的內容作為block對象呈現,之后的內聯對象會被排列在同一行內。
對于行內元素和塊級元素,其特點如下:
(1)行內元素
- 設置寬高無效;
- 可以設置水平方向的margin和padding屬性,不能設置垂直方向的padding和margin;
- 不會自動換行;
(2)塊級元素
- 可以設置寬高;
- 設置margin和padding都有效;
- 可以自動換行;
- 多個塊狀,默認排列從上到下。
如何判斷一個對象是不是空對象?
Object.keys(obj).length === 0
手寫題:在線編程,getUrlParams(url,key); 就是很簡單的獲取url的某個參數的問題,但要考慮邊界情況,多個返回值等等
偽元素和偽類的區(qū)別和作用?
- 偽元素:在內容元素的前后插入額外的元素或樣式,但是這些元素實際上并不在文檔中生成。它們只在外部顯示可見,但不會在文檔的源代碼中找到它們,因此,稱為“偽”元素。例如:
p::before {content:"第一章:";}
p::after {content:"Hot!";}
p::first-line {background:red;}
p::first-letter {font-size:30px;}
- 偽類:將特殊的效果添加到特定選擇器上。它是已有元素上添加類別的,不會產生新的元素。例如:
a:hover {color: #FF00FF}
p:first-child {color: red}
總結: 偽類是通過在元素選擇器上加?偽類改變元素狀態(tài),?偽元素通過對元素的操作進?對元素的改變。
TCP的三次握手和四次揮手
(1)三次握手
三次握手(Three-way Handshake)其實就是指建立一個TCP連接時,需要客戶端和服務器總共發(fā)送3個包。進行三次握手的主要作用就是為了確認雙方的接收能力和發(fā)送能力是否正常、指定自己的初始化序列號為后面的可靠性傳送做準備。實質上其實就是連接服務器指定端口,建立TCP連接,并同步連接雙方的序列號和確認號,交換TCP窗口大小信息。
剛開始客戶端處于 Closed 的狀態(tài),服務端處于 Listen 狀態(tài)。
- 第一次握手:客戶端給服務端發(fā)一個 SYN 報文,并指明客戶端的初始化序列號 ISN,此時客戶端處于 SYN_SEND 狀態(tài)。
首部的同步位SYN=1,初始序號seq=x,SYN=1的報文段不能攜帶數據,但要消耗掉一個序號。
- 第二次握手:服務器收到客戶端的 SYN 報文之后,會以自己的 SYN 報文作為應答,并且也是指定了自己的初始化序列號 ISN。同時會把客戶端的 ISN + 1 作為ACK 的值,表示自己已經收到了客戶端的 SYN,此時服務器處于 SYN_REVD 的狀態(tài)。
在確認報文段中SYN=1,ACK=1,確認號ack=x+1,初始序號seq=y
- 第三次握手:客戶端收到 SYN 報文之后,會發(fā)送一個 ACK 報文,當然,也是一樣把服務器的 ISN + 1 作為 ACK 的值,表示已經收到了服務端的 SYN 報文,此時客戶端處于 ESTABLISHED 狀態(tài)。服務器收到 ACK 報文之后,也處于 ESTABLISHED 狀態(tài),此時,雙方已建立起了連接。
確認報文段ACK=1,確認號ack=y+1,序號seq=x+1(初始為seq=x,第二個報文段所以要+1),ACK報文段可以攜帶數據,不攜帶數據則不消耗序號。
那為什么要三次握手呢?兩次不行嗎?
- 為了確認雙方的接收能力和發(fā)送能力都正常
- 如果是用兩次握手,則會出現下面這種情況:
如客戶端發(fā)出連接請求,但因連接請求報文丟失而未收到確認,于是客戶端再重傳一次連接請求。后來收到了確認,建立了連接。數據傳輸完畢后,就釋放了連接,客戶端共發(fā)出了兩個連接請求報文段,其中第一個丟失,第二個到達了服務端,但是第一個丟失的報文段只是在某些網絡結點長時間滯留了,延誤到連接釋放以后的某個時間才到達服務端,此時服務端誤認為客戶端又發(fā)出一次新的連接請求,于是就向客戶端發(fā)出確認報文段,同意建立連接,不采用三次握手,只要服務端發(fā)出確認,就建立新的連接了,此時客戶端忽略服務端發(fā)來的確認,也不發(fā)送數據,則服務端一致等待客戶端發(fā)送數據,浪費資源。
簡單來說就是以下三步:
- 第一次握手: 客戶端向服務端發(fā)送連接請求報文段。該報文段中包含自身的數據通訊初始序號。請求發(fā)送后,客戶端便進入 SYN-SENT 狀態(tài)。
- 第二次握手: 服務端收到連接請求報文段后,如果同意連接,則會發(fā)送一個應答,該應答中也會包含自身的數據通訊初始序號,發(fā)送完成后便進入 SYN-RECEIVED 狀態(tài)。
- 第三次握手: 當客戶端收到連接同意的應答后,還要向服務端發(fā)送一個確認報文??蛻舳税l(fā)完這個報文段后便進入 ESTABLISHED 狀態(tài),服務端收到這個應答后也進入 ESTABLISHED 狀態(tài),此時連接建立成功。
TCP 三次握手的建立連接的過程就是相互確認初始序號的過程,告訴對方,什么樣序號的報文段能夠被正確接收。 第三次握手的作用是客戶端對服務器端的初始序號的確認。如果只使用兩次握手,那么服務器就沒有辦法知道自己的序號是否 已被確認。同時這樣也是為了防止失效的請求報文段被服務器接收,而出現錯誤的情況。
(2)四次揮手
剛開始雙方都處于 ESTABLISHED 狀態(tài),假如是客戶端先發(fā)起關閉請求。四次揮手的過程如下:
- 第一次揮手: 客戶端會發(fā)送一個 FIN 報文,報文中會指定一個序列號。此時客戶端處于 FIN_WAIT1 狀態(tài)。
即發(fā)出連接釋放報文段(FIN=1,序號seq=u),并停止再發(fā)送數據,主動關閉TCP連接,進入FIN_WAIT1(終止等待1)狀態(tài),等待服務端的確認。
- 第二次揮手:服務端收到 FIN 之后,會發(fā)送 ACK 報文,且把客戶端的序列號值 +1 作為 ACK 報文的序列號值,表明已經收到客戶端的報文了,此時服務端處于 CLOSE_WAIT 狀態(tài)。
即服務端收到連接釋放報文段后即發(fā)出確認報文段(ACK=1,確認號ack=u+1,序號seq=v),服務端進入CLOSE_WAIT(關閉等待)狀態(tài),此時的TCP處于半關閉狀態(tài),客戶端到服務端的連接釋放??蛻舳耸盏椒斩说拇_認后,進入FIN_WAIT2(終止等待2)狀態(tài),等待服務端發(fā)出的連接釋放報文段。
- 第三次揮手:如果服務端也想斷開連接了,和客戶端的第一次揮手一樣,發(fā)給 FIN 報文,且指定一個序列號。此時服務端處于 LAST_ACK 的狀態(tài)。
即服務端沒有要向客戶端發(fā)出的數據,服務端發(fā)出連接釋放報文段(FIN=1,ACK=1,序號seq=w,確認號ack=u+1),服務端進入LAST_ACK(最后確認)狀態(tài),等待客戶端的確認。
- 第四次揮手:客戶端收到 FIN 之后,一樣發(fā)送一個 ACK 報文作為應答,且把服務端的序列號值 +1 作為自己 ACK 報文的序列號值,此時客戶端處于 TIME_WAIT 狀態(tài)。需要過一陣子以確保服務端收到自己的 ACK 報文之后才會進入 CLOSED 狀態(tài),服務端收到 ACK 報文之后,就處于關閉連接了,處于 CLOSED 狀態(tài)。
即客戶端收到服務端的連接釋放報文段后,對此發(fā)出確認報文段(ACK=1,seq=u+1,ack=w+1),客戶端進入TIME_WAIT(時間等待)狀態(tài)。此時TCP未釋放掉,需要經過時間等待計時器設置的時間2MSL后,客戶端才進入CLOSED狀態(tài)。
那為什么需要四次揮手呢?
因為當服務端收到客戶端的SYN連接請求報文后,可以直接發(fā)送SYN+ACK報文。其中ACK報文是用來應答的,SYN報文是用來同步的。但是關閉連接時,當服務端收到FIN報文時,很可能并不會立即關閉SOCKET,所以只能先回復一個ACK報文,告訴客戶端,“你發(fā)的FIN報文我收到了”。只有等到我服務端所有的報文都發(fā)送完了,我才能發(fā)送FIN報文,因此不能一起發(fā)送,故需要四次揮手。
簡單來說就是以下四步:
- 第一次揮手: 若客戶端認為數據發(fā)送完成,則它需要向服務端發(fā)送連接釋放請求。
- 第二次揮手:服務端收到連接釋放請求后,會告訴應用層要釋放 TCP 鏈接。然后會發(fā)送 ACK 包,并進入 CLOSE_WAIT 狀態(tài),此時表明客戶端到服務端的連接已經釋放,不再接收客戶端發(fā)的數據了。但是因為 TCP 連接是雙向的,所以服務端仍舊可以發(fā)送數據給客戶端。
- 第三次揮手:服務端如果此時還有沒發(fā)完的數據會繼續(xù)發(fā)送,完畢后會向客戶端發(fā)送連接釋放請求,然后服務端便進入 LAST-ACK 狀態(tài)。
- 第四次揮手: 客戶端收到釋放請求后,向服務端發(fā)送確認應答,此時客戶端進入 TIME-WAIT 狀態(tài)。該狀態(tài)會持續(xù) 2MSL(最大段生存期,指報文段在網絡中生存的時間,超時會被拋棄) 時間,若該時間段內沒有服務端的重發(fā)請求的話,就進入 CLOSED 狀態(tài)。當服務端收到確認應答后,也便進入 CLOSED 狀態(tài)。
TCP 使用四次揮手的原因是因為 TCP 的連接是全雙工的,所以需要雙方分別釋放到對方的連接,單獨一方的連接釋放,只代 表不能再向對方發(fā)送數據,連接處于的是半釋放的狀態(tài)。
最后一次揮手中,客戶端會等待一段時間再關閉的原因,是為了防止發(fā)送給服務器的確認報文段丟失或者出錯,從而導致服務器 端不能正常關閉。
什么是閉包
閉包是一種特殊的對象,它由兩部分組成:執(zhí)行上下文(代號 A),以及在該執(zhí)行上下文中創(chuàng)建的函數 (代號 B),當 B 執(zhí)行時,如果訪問了 A 中變量對象的值,那么閉包就會產生,且在 Chrome 中使用這個執(zhí)行上下文 A 的函數名代指閉包。
正向代理和反向代理的區(qū)別
- 正向代理:
客戶端想獲得一個服務器的數據,但是因為種種原因無法直接獲取。于是客戶端設置了一個代理服務器,并且指定目標服務器,之后代理服務器向目標服務器轉交請求并將獲得的內容發(fā)送給客戶端。這樣本質上起到了對真實服務器隱藏真實客戶端的目的。實現正向代理需要修改客戶端,比如修改瀏覽器配置。
- 反向代理:
服務器為了能夠將工作負載分不到多個服務器來提高網站性能 (負載均衡)等目的,當其受到請求后,會首先根據轉發(fā)規(guī)則來確定請求應該被轉發(fā)到哪個服務器上,然后將請求轉發(fā)到對應的真實服務器上。這樣本質上起到了對客戶端隱藏真實服務器的作用。
一般使用反向代理后,需要通過修改 DNS 讓域名解析到代理服務器 IP,這時瀏覽器無法察覺到真正服務器的存在,當然也就不需要修改配置了。
正向代理和反向代理的結構是一樣的,都是 client-proxy-server 的結構,它們主要的區(qū)別就在于中間這個 proxy 是哪一方設置的。在正向代理中,proxy 是 client 設置的,用來隱藏 client;而在反向代理中,proxy 是 server 設置的,用來隱藏 server。
typeof null 的結果是什么,為什么?
typeof null 的結果是Object。
在 JavaScript 第一個版本中,所有值都存儲在 32 位的單元中,每個單元包含一個小的 類型標簽(1-3 bits) 以及當前要存儲值的真實數據。類型標簽存儲在每個單元的低位中,共有五種數據類型:
000: object - 當前存儲的數據指向一個對象。
1: int - 當前存儲的數據是一個 31 位的有符號整數。
010: double - 當前存儲的數據指向一個雙精度的浮點數。
100: string - 當前存儲的數據指向一個字符串。
110: boolean - 當前存儲的數據是布爾值。
如果最低位是 1,則類型標簽標志位的長度只有一位;如果最低位是 0,則類型標簽標志位的長度占三位,為存儲其他四種數據類型提供了額外兩個 bit 的長度。
有兩種特殊數據類型:
- undefined的值是 (-2)30(一個超出整數范圍的數字);
- null 的值是機器碼 NULL 指針(null 指針的值全是 0)
那也就是說null的類型標簽也是000,和Object的類型標簽一樣,所以會被判定為Object。
HTTP狀態(tài)碼
狀態(tài)碼的類別:
| 類別 | 原因 | 描述 |
|---|---|---|
| 1xx | Informational(信息性狀態(tài)碼) | 接受的請求正在處理 |
| 2xx | Success(成功狀態(tài)碼) | 請求正常處理完畢 |
| 3xx | Redirection(重定向狀態(tài)碼) | 需要進行附加操作一完成請求 |
| 4xx | Client Error (客戶端錯誤狀態(tài)碼) | 服務器無法處理請求 |
| 5xx | Server Error(服務器錯誤狀態(tài)碼) | 服務器處理請求出錯 |
1. 2XX (Success 成功狀態(tài)碼)
狀態(tài)碼2XX表示請求被正常處理了。
(1)200 OK
200 OK表示客戶端發(fā)來的請求被服務器端正常處理了。
(2)204 No Content
該狀態(tài)碼表示客戶端發(fā)送的請求已經在服務器端正常處理了,但是沒有返回的內容,響應報文中不包含實體的主體部分。一般在只需要從客戶端往服務器端發(fā)送信息,而服務器端不需要往客戶端發(fā)送內容時使用。
(3)206 Partial Content
該狀態(tài)碼表示客戶端進行了范圍請求,而服務器端執(zhí)行了這部分的 GET 請求。響應報文中包含由 Content-Range 指定范圍的實體內容。
2. 3XX (Redirection 重定向狀態(tài)碼)
3XX 響應結果表明瀏覽器需要執(zhí)行某些特殊的處理以正確處理請求。
(1)301 Moved Permanently
永久重定向。 該狀態(tài)碼表示請求的資源已經被分配了新的 URI,以后應使用資源指定的 URI。新的 URI 會在 HTTP 響應頭中的 Location 首部字段指定。若用戶已經把原來的URI保存為書簽,此時會按照 Location 中新的URI重新保存該書簽。同時,搜索引擎在抓取新內容的同時也將舊的網址替換為重定向之后的網址。
使用場景:
- 當我們想換個域名,舊的域名不再使用時,用戶訪問舊域名時用301就重定向到新的域名。其實也是告訴搜索引擎收錄的域名需要對新的域名進行收錄。
- 在搜索引擎的搜索結果中出現了不帶www的域名,而帶www的域名卻沒有收錄,這個時候可以用301重定向來告訴搜索引擎我們目標的域名是哪一個。
(2)302 Found
臨時重定向。 該狀態(tài)碼表示請求的資源被分配到了新的 URI,希望用戶(本次)能使用新的 URI 訪問資源。和 301 Moved Permanently 狀態(tài)碼相似,但是 302 代表的資源不是被永久重定向,只是臨時性質的。也就是說已移動的資源對應的 URI 將來還有可能發(fā)生改變。若用戶把 URI 保存成書簽,但不會像 301 狀態(tài)碼出現時那樣去更新書簽,而是仍舊保留返回 302 狀態(tài)碼的頁面對應的 URI。同時,搜索引擎會抓取新的內容而保留舊的網址。因為服務器返回302代碼,搜索引擎認為新的網址只是暫時的。
使用場景:
- 當我們在做活動時,登錄到首頁自動重定向,進入活動頁面。
- 未登陸的用戶訪問用戶中心重定向到登錄頁面。
- 訪問404頁面重新定向到首頁。
(3)303 See Other
該狀態(tài)碼表示由于請求對應的資源存在著另一個 URI,應使用 GET 方法定向獲取請求的資源。
303 狀態(tài)碼和 302 Found 狀態(tài)碼有著相似的功能,但是 303 狀態(tài)碼明確表示客戶端應當采用 GET 方法獲取資源。
303 狀態(tài)碼通常作為 PUT 或 POST 操作的返回結果,它表示重定向鏈接指向的不是新上傳的資源,而是另外一個頁面,比如消息確認頁面或上傳進度頁面。而請求重定向頁面的方法要總是使用 GET。
注意:
- 當 301、302、303 響應狀態(tài)碼返回時,幾乎所有的瀏覽器都會把 POST 改成GET,并刪除請求報文內的主體,之后請求會再次自動發(fā)送。
- 301、302 標準是禁止將 POST 方法變成 GET方法的,但實際大家都會這么做。
(4)304 Not Modified
瀏覽器緩存相關。 該狀態(tài)碼表示客戶端發(fā)送附帶條件的請求時,服務器端允許請求訪問資源,但未滿足條件的情況。304 狀態(tài)碼返回時,不包含任何響應的主體部分。304 雖然被劃分在 3XX 類別中,但是和重定向沒有關系。
帶條件的請求(Http 條件請求):使用 Get方法 請求,請求報文中包含(if-match、if-none-match、if-modified-since、if-unmodified-since、if-range)中任意首部。
狀態(tài)碼304并不是一種錯誤,而是告訴客戶端有緩存,直接使用緩存中的數據。返回頁面的只有頭部信息,是沒有內容部分的,這樣在一定程度上提高了網頁的性能。
(5)307 Temporary Redirect
307表示臨時重定向。 該狀態(tài)碼與 302 Found 有著相同含義,盡管 302 標準禁止 POST 變成 GET,但是實際使用時還是這樣做了。
307 會遵守瀏覽器標準,不會從 POST 變成 GET。但是對于處理請求的行為時,不同瀏覽器還是會出現不同的情況。規(guī)范要求瀏覽器繼續(xù)向 Location 的地址 POST 內容。規(guī)范要求瀏覽器繼續(xù)向 Location 的地址 POST 內容。
3. 4XX (Client Error 客戶端錯誤狀態(tài)碼)
4XX 的響應結果表明客戶端是發(fā)生錯誤的原因所在。
(1)400 Bad Request
該狀態(tài)碼表示請求報文中存在語法錯誤。當錯誤發(fā)生時,需修改請求的內容后再次發(fā)送請求。另外,瀏覽器會像 200 OK 一樣對待該狀態(tài)碼。
(2)401 Unauthorized
該狀態(tài)碼表示發(fā)送的請求需要有通過 HTTP 認證(BASIC 認證、DIGEST 認證)的認證信息。若之前已進行過一次請求,則表示用戶認證失敗
返回含有 401 的響應必須包含一個適用于被請求資源的 WWW-Authenticate 首部用以質詢(challenge)用戶信息。當瀏覽器初次接收到 401 響應,會彈出認證用的對話窗口。
以下情況會出現401:
- 401.1 - 登錄失敗。
- 401.2 - 服務器配置導致登錄失敗。
- 401.3 - 由于 ACL 對資源的限制而未獲得授權。
- 401.4 - 篩選器授權失敗。
- 401.5 - ISAPI/CGI 應用程序授權失敗。
- 401.7 - 訪問被 Web 服務器上的 URL 授權策略拒絕。這個錯誤代碼為 IIS 6.0 所專用。
(3)403 Forbidden
該狀態(tài)碼表明請求資源的訪問被服務器拒絕了,服務器端沒有必要給出詳細理由,但是可以在響應報文實體的主體中進行說明。進入該狀態(tài)后,不能再繼續(xù)進行驗證。該訪問是永久禁止的,并且與應用邏輯密切相關。
IIS 定義了許多不同的 403 錯誤,它們指明更為具體的錯誤原因:
- 403.1 - 執(zhí)行訪問被禁止。
- 403.2 - 讀訪問被禁止。
- 403.3 - 寫訪問被禁止。
- 403.4 - 要求 SSL。
- 403.5 - 要求 SSL 128。
- 403.6 - IP 地址被拒絕。
- 403.7 - 要求客戶端證書。
- 403.8 - 站點訪問被拒絕。
- 403.9 - 用戶數過多。
- 403.10 - 配置無效。
- 403.11 - 密碼更改。
- 403.12 - 拒絕訪問映射表。
- 403.13 - 客戶端證書被吊銷。
- 403.14 - 拒絕目錄列表。
- 403.15 - 超出客戶端訪問許可。
- 403.16 - 客戶端證書不受信任或無效。
- 403.17 - 客戶端證書已過期或尚未生效
- 403.18 - 在當前的應用程序池中不能執(zhí)行所請求的 URL。這個錯誤代碼為 IIS 6.0 所專用。
- 403.19 - 不能為這個應用程序池中的客戶端執(zhí)行 CGI。這個錯誤代碼為 IIS 6.0 所專用。
- 403.20 - Passport 登錄失敗。這個錯誤代碼為 IIS 6.0 所專用。
(4)404 Not Found
該狀態(tài)碼表明服務器上無法找到請求的資源。除此之外,也可以在服務器端拒絕請求且不想說明理由時使用。
以下情況會出現404:
- 404.0 -(無) – 沒有找到文件或目錄。
- 404.1 - 無法在所請求的端口上訪問 Web 站點。
- 404.2 - Web 服務擴展鎖定策略阻止本請求。
- 404.3 - MIME 映射策略阻止本請求。
(5)405 Method Not Allowed
該狀態(tài)碼表示客戶端請求的方法雖然能被服務器識別,但是服務器禁止使用該方法。GET 和 HEAD 方法,服務器應該總是允許客戶端進行訪問。客戶端可以通過 OPTIONS 方法(預檢)來查看服務器允許的訪問方法, 如下
Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE
4. 5XX (Server Error 服務器錯誤狀態(tài)碼)
5XX 的響應結果表明服務器本身發(fā)生錯誤.
(1)500 Internal Server Error
該狀態(tài)碼表明服務器端在執(zhí)行請求時發(fā)生了錯誤。也有可能是 Web 應用存在的 bug 或某些臨時的故障。
(2)502 Bad Gateway
該狀態(tài)碼表明扮演網關或代理角色的服務器,從上游服務器中接收到的響應是無效的。注意,502 錯誤通常不是客戶端能夠修復的,而是需要由途經的 Web 服務器或者代理服務器對其進行修復。以下情況會出現502:
- 502.1 - CGI (通用網關接口)應用程序超時。
- 502.2 - CGI (通用網關接口)應用程序出錯。
(3)503 Service Unavailable
該狀態(tài)碼表明服務器暫時處于超負載或正在進行停機維護,現在無法處理請求。如果事先得知解除以上狀況需要的時間,最好寫入 RetryAfter 首部字段再返回給客戶端。
使用場景:
- 服務器停機維護時,主動用503響應請求;
- nginx 設置限速,超過限速,會返回503。
(4)504 Gateway Timeout
該狀態(tài)碼表示網關或者代理的服務器無法在規(guī)定的時間內獲得想要的響應。他是HTTP 1.1中新加入的。
使用場景:代碼執(zhí)行時間超時,或者發(fā)生了死循環(huán)。
5. 總結
(1)2XX 成功
- 200 OK,表示從客戶端發(fā)來的請求在服務器端被正確處理
- 204 No content,表示請求成功,但響應報文不含實體的主體部分
- 205 Reset Content,表示請求成功,但響應報文不含實體的主體部分,但是與 204 響應不同在于要求請求方重置內容
- 206 Partial Content,進行范圍請求
(2)3XX 重定向
- 301 moved permanently,永久性重定向,表示資源已被分配了新的 URL
- 302 found,臨時性重定向,表示資源臨時被分配了新的 URL
- 303 see other,表示資源存在著另一個 URL,應使用 GET 方法獲取資源
- 304 not modified,表示服務器允許訪問資源,但因發(fā)生請求未滿足條件的情況
- 307 temporary redirect,臨時重定向,和302含義類似,但是期望客戶端保持請求方法不變向新的地址發(fā)出請求
(3)4XX 客戶端錯誤
- 400 bad request,請求報文存在語法錯誤
- 401 unauthorized,表示發(fā)送的請求需要有通過 HTTP 認證的認證信息
- 403 forbidden,表示對請求資源的訪問被服務器拒絕
- 404 not found,表示在服務器上沒有找到請求的資源
(4)5XX 服務器錯誤
- 500 internal sever error,表示服務器端在執(zhí)行請求時發(fā)生了錯誤
- 501 Not Implemented,表示服務器不支持當前請求所需要的某個功能
- 503 service unavailable,表明服務器暫時處于超負載或正在停機維護,無法處理請求
原型鏈指向
p.__proto__ // Person.prototype
Person.prototype.__proto__ // Object.prototype
p.__proto__.__proto__ //Object.prototype
p.__proto__.constructor.prototype.__proto__ // Object.prototype
Person.prototype.constructor.prototype.__proto__ // Object.prototype
p1.__proto__.constructor // Person
Person.prototype.constructor // Person
對HTML語義化的理解
語義化是指根據內容的結構化(內容語義化),選擇合適的標簽(代碼語義化)。通俗來講就是用正確的標簽做正確的事情。
語義化的優(yōu)點如下:
- 對機器友好,帶有語義的文字表現力豐富,更適合搜索引擎的爬蟲爬取有效信息,有利于SEO。除此之外,語義類還支持讀屏軟件,根據文章可以自動生成目錄;
- 對開發(fā)者友好,使用語義類標簽增強了可讀性,結構更加清晰,開發(fā)者能清晰的看出網頁的結構,便于團隊的開發(fā)與維護。
常見的語義化標簽:
<header></header> 頭部
<nav></nav> 導航欄
<section></section> 區(qū)塊(有語義化的div)
<main></main> 主要區(qū)域
<article></article> 主要內容
<aside></aside> 側邊欄
<footer></footer> 底部
如何防御 CSRF 攻擊?
CSRF 攻擊可以使用以下方法來防護:
- 進行同源檢測,服務器根據 http 請求頭中 origin 或者 referer 信息來判斷請求是否為允許訪問的站點,從而對請求進行過濾。當 origin 或者 referer 信息都不存在的時候,直接阻止請求。這種方式的缺點是有些情況下 referer 可以被偽造,同時還會把搜索引擎的鏈接也給屏蔽了。所以一般網站會允許搜索引擎的頁面請求,但是相應的頁面請求這種請求方式也可能被攻擊者給利用。(Referer 字段會告訴服務器該網頁是從哪個頁面鏈接過來的)
- 使用 CSRF Token 進行驗證,服務器向用戶返回一個隨機數 Token ,當網站再次發(fā)起請求時,在請求參數中加入服務器端返回的 token ,然后服務器對這個 token 進行驗證。這種方法解決了使用 cookie 單一驗證方式時,可能會被冒用的問題,但是這種方法存在一個缺點就是,我們需要給網站中的所有請求都添加上這個 token,操作比較繁瑣。還有一個問題是一般不會只有一臺網站服務器,如果請求經過負載平衡轉移到了其他的服務器,但是這個服務器的 session 中沒有保留這個 token 的話,就沒有辦法驗證了。這種情況可以通過改變 token 的構建方式來解決。
- 對 Cookie 進行雙重驗證,服務器在用戶訪問網站頁面時,向請求域名注入一個Cookie,內容為隨機字符串,然后當用戶再次向服務器發(fā)送請求的時候,從 cookie 中取出這個字符串,添加到 URL 參數中,然后服務器通過對 cookie 中的數據和參數中的數據進行比較,來進行驗證。使用這種方式是利用了攻擊者只能利用 cookie,但是不能訪問獲取 cookie 的特點。并且這種方法比 CSRF Token 的方法更加方便,并且不涉及到分布式訪問的問題。這種方法的缺點是如果網站存在 XSS 漏洞的,那么這種方式會失效。同時這種方式不能做到子域名的隔離。
- 在設置 cookie 屬性的時候設置 Samesite ,限制 cookie 不能作為被第三方使用,從而可以避免被攻擊者利用。Samesite 一共有兩種模式,一種是嚴格模式,在嚴格模式下 cookie 在任何情況下都不可能作為第三方 Cookie 使用,在寬松模式下,cookie 可以被請求是 GET 請求,且會發(fā)生頁面跳轉的請求所使用。
new 操作符
題目描述:手寫 new 操作符實現
實現代碼如下:
function myNew(fn, ...args) {
let obj = Object.create(fn.prototype);
let res = fn.call(obj, ...args);
if (res && (typeof res === "object" || typeof res === "function")) {
return res;
}
return obj;
}
用法如下:
// // function Person(name, age) {
// // this.name = name;
// // this.age = age;
// // }
// // Person.prototype.say = function() {
// // console.log(this.age);
// // };
// // let p1 = myNew(Person, "lihua", 18);
// // console.log(p1.name);
// // console.log(p1);
// // p1.say();
請實現 DOM2JSON 一個函數,可以把一個 DOM 節(jié)點輸出 JSON 的格式
題目描述:
<div>
<span>
<a></a>
</span>
<span>
<a></a>
<a></a>
</span>
</div>
把上訴dom結構轉成下面的JSON格式
{
tag: 'DIV',
children: [
{
tag: 'SPAN',
children: [
{ tag: 'A', children: [] }
]
},
{
tag: 'SPAN',
children: [
{ tag: 'A', children: [] },
{ tag: 'A', children: [] }
]
}
]
}
實現代碼如下:
function dom2Json(domtree) {
let obj = {};
obj.name = domtree.tagName;
obj.children = [];
domtree.childNodes.forEach((child) => obj.children.push(dom2Json(child)));
return obj;
}
擴展思考:如果給定的不是一個 Dom 樹結構 而是一段 html 字符串 該如何解析?
那么這個問題就類似 Vue 的模板編譯原理 我們可以利用正則 匹配 html 字符串 遇到開始標簽 結束標簽和文本 解析完畢之后生成對應的 ast 并建立相應的父子關聯 不斷的 advance 截取剩余的字符串 直到 html 全部解析完畢
CSS 如何阻塞文檔解析?
理論上,既然樣式表不改變 DOM 樹,也就沒有必要停下文檔的解析等待它們。然而,存在一個問題,JavaScript 腳本執(zhí)行時可能在文檔的解析過程中請求樣式信息,如果樣式還沒有加載和解析,腳本將得到錯誤的值,顯然這將會導致很多問題。所以如果瀏覽器尚未完成 CSSOM 的下載和構建,而我們卻想在此時運行腳本,那么瀏覽器將延遲 JavaScript 腳本執(zhí)行和文檔的解析,直至其完成 CSSOM 的下載和構建。也就是說,在這種情況下,瀏覽器會先下載和構建 CSSOM,然后再執(zhí)行 JavaScript,最后再繼續(xù)文檔的解析。
瀏覽器亂碼的原因是什么?如何解決?
產生亂碼的原因:
- 網頁源代碼是
gbk的編碼,而內容中的中文字是utf-8編碼的,這樣瀏覽器打開即會出現html亂碼,反之也會出現亂碼; -
html網頁編碼是gbk,而程序從數據庫中調出呈現是utf-8編碼的內容也會造成編碼亂碼; - 瀏覽器不能自動檢測網頁編碼,造成網頁亂碼。
解決辦法:
- 使用軟件編輯HTML網頁內容;
- 如果網頁設置編碼是
gbk,而數據庫儲存數據編碼格式是UTF-8,此時需要程序查詢數據庫數據顯示數據前進程序轉碼; - 如果瀏覽器瀏覽時候出現網頁亂碼,在瀏覽器中找到轉換編碼的菜單進行轉換。