美團前端常考面試題(必備)
跨域方案
很多種方法,但萬變不離其宗,都是為了搞定同源策略。重用的有
jsonp
、iframe
、cors
、img
、HTML5 postMessage
等等。其中用到html
標籤進行跨域的原理就是html
不受同源策略影響。但只是接受Get
的請求方式,這個得清楚。延伸1:img iframe script 來發送跨域請求有什麼優缺點?
1. iframe
- 優點:跨域完畢之後
DOM
操作和互相之間的JavaScript
呼叫都是沒有問題的 - 缺點:1.若結果要以
URL
引數傳遞,這就意味著在結果資料量很大的時候需要分割傳遞,巨煩。2.還有一個是iframe
本身帶來的,母頁面和iframe
本身的互動本身就有安全性限制。
2. script
- 優點:可以直接返回
json
格式的資料,方便處理 - 缺點:只接受
GET
請求方式
3. 圖片ping
- 優點:可以訪問任何
url
,一般用來進行點選追蹤,做頁面分析常用的方法 - 缺點:不能訪問響應文字,只能監聽是否響應
延伸2:配合 webpack 進行反向代理?
webpack
在 devServer
選項裡面提供了一個 proxy
的引數供開發人員進行反向代理
javascript
'/api': {
target: 'http://www.example.com', // your target host
changeOrigin: true, // needed for virtual hosted sites
pathRewrite: {
'^/api': '' // rewrite path
}
},
然後再配合
http-proxy-middleware
外掛對api
請求地址進行代理
```javascript const express = require('express'); const proxy = require('http-proxy-middleware'); // proxy api requests const exampleProxy = proxy(options); // 這裡的 options 就是 webpack 裡面的 proxy 選項對應的每個選項
// mount exampleProxy
in web server
const app = express();
app.use('/api', exampleProxy);
app.listen(3000);
```
然後再用
nginx
把允許跨域的源地址新增到報頭裡面即可說到
nginx
,可以再談談CORS
配置,大致如下
javascript
location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Headers' 'DNT, X-Mx-ReqToken, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type';
add_header 'Access-Control-Max-Age' 86400;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 200;
}
}
createElement過程
React.createElement(): 根據指定的第一個引數建立一個React元素
javascript
React.createElement(
type,
[props],
[...children]
)
- 第一個引數是必填,傳入的是似HTML標籤名稱,eg: ul, li
- 第二個引數是選填,表示的是屬性,eg: className
- 第三個引數是選填, 子節點,eg: 要顯示的文字內容
```javascript //寫法一:
var child1 = React.createElement('li', null, 'one'); var child2 = React.createElement('li', null, 'two'); var content = React.createElement('ul', { className: 'teststyle' }, child1, child2); // 第三個引數可以分開也可以寫成一個數組 ReactDOM.render( content, document.getElementById('example') );
//寫法二:
var child1 = React.createElement('li', null, 'one'); var child2 = React.createElement('li', null, 'two'); var content = React.createElement('ul', { className: 'teststyle' }, [child1, child2]); ReactDOM.render( content, document.getElementById('example') ); ```
TCP/IP五層協議
TCP/IP
五層協議和OSI
的七層協議對應關係如下:
- 應用層 (application layer):直接為應用程序提供服務。應用層協議定義的是應用程序間通訊和互動的規則,不同的應用有著不同的應用層協議,如 HTTP協議(全球資訊網服務)、FTP協議(檔案傳輸)、SMTP協議(電子郵件)、DNS(域名查詢)等。
- 傳輸層 (transport layer):有時也譯為運輸層,它負責為兩臺主機中的程序提供通訊服務。該層主要有以下兩種協議:
- 傳輸控制協議 (Transmission Control Protocol,TCP):提供面向連線的、可靠的資料傳輸服務,資料傳輸的基本單位是報文段(segment);
- 使用者資料報協議 (User Datagram Protocol,UDP):提供無連線的、盡最大努力的資料傳輸服務,但不保證資料傳輸的可靠性,資料傳輸的基本單位是使用者資料報。
- 網路層 (internet layer):有時也譯為網際層,它負責為兩臺主機提供通訊服務,並通過選擇合適的路由將資料傳遞到目標主機。
- 資料鏈路層 (data link layer):負責將網路層交下來的 IP 資料報封裝成幀,並在鏈路的兩個相鄰節點間傳送幀,每一幀都包含資料和必要的控制資訊(如同步資訊、地址資訊、差錯控制等)。
- 物理層 (physical Layer):確保資料可以在各種物理媒介上進行傳輸,為資料的傳輸提供可靠的環境。
從上圖中可以看出,TCP/IP
模型比OSI
模型更加簡潔,它把應用層/表示層/會話層
全部整合為了應用層
。
在每一層都工作著不同的裝置,比如我們常用的交換機就工作在資料鏈路層的,一般的路由器是工作在網路層的。 在每一層實現的協議也各不同,即每一層的服務也不同,下圖列出了每層主要的傳輸協議:
同樣,TCP/IP
五層協議的通訊方式也是對等通訊:
事件機制
涉及面試題:事件的觸發過程是怎麼樣的?知道什麼是事件代理嘛?
1. 簡介
事件流是一個事件沿著特定資料結構傳播的過程。冒泡和捕獲是事件流在
DOM
中兩種不同的傳播方法
事件流有三個階段
- 事件捕獲階段
- 處於目標階段
- 事件冒泡階段
事件捕獲
事件捕獲(
event capturing
):通俗的理解就是,當滑鼠點選或者觸發dom
事件時,瀏覽器會從根節點開始由外到內進行事件傳播,即點選了子元素,如果父元素通過事件捕獲方式註冊了對應的事件的話,會先觸發父元素繫結的事件
事件冒泡
事件冒泡(dubbed bubbling):與事件捕獲恰恰相反,事件冒泡順序是由內到外進行事件傳播,直到根節點
無論是事件捕獲還是事件冒泡,它們都有一個共同的行為,就是事件傳播
2. 捕獲和冒泡
```html
```
當點選
div2
時,會彈出兩個彈出框。在ie8/9/10
、chrome
瀏覽器,會先彈出”2”再彈出“1”,這就是事件冒泡:事件從最底層的節點向上冒泡傳播。事件捕獲則跟事件冒泡相反W3C的標準是先捕獲再冒泡,
addEventListener
的第三個引數決定把事件註冊在捕獲(true
)還是冒泡(false
)
3. 事件物件
4. 事件流阻止
在一些情況下需要阻止事件流的傳播,阻止預設動作的發生
event.preventDefault()
:取消事件物件的預設動作以及繼續傳播。event.stopPropagation()/ event.cancelBubble = true
:阻止事件冒泡。
事件的阻止在不同瀏覽器有不同處理
- 在
IE
下使用event.returnValue= false
, - 在非
IE
下則使用event.preventDefault()
進行阻止
preventDefault與stopPropagation的區別
preventDefault
告訴瀏覽器不用執行與事件相關聯的預設動作(如表單提交)stopPropagation
是停止事件繼續冒泡,但是對IE9以下的瀏覽器無效
5. 事件註冊
- 通常我們使用
addEventListener
註冊事件,該函式的第三個引數可以是布林值,也可以是物件。對於布林值useCapture
引數來說,該引數預設值為false
。useCapture
決定了註冊的事件是捕獲事件還是冒泡事件 - 一般來說,我們只希望事件只觸發在目標上,這時候可以使用
stopPropagation
來阻止事件的進一步傳播。通常我們認為stopPropagation
是用來阻止事件冒泡的,其實該函式也可以阻止捕獲事件。stopImmediatePropagation
同樣也能實現阻止事件,但是還能阻止該事件目標執行別的註冊事件
javascript
node.addEventListener('click',(event) =>{
event.stopImmediatePropagation()
console.log('冒泡')
},false);
// 點選 node 只會執行上面的函式,該函式不會執行
node.addEventListener('click',(event) => {
console.log('捕獲 ')
},true)
6. 事件委託
- 在
js
中效能優化的其中一個主要思想是減少dom
操作。 - 節省記憶體
- 不需要給子節點登出事件
假設有
100
個li
,每個li
有相同的點選事件。如果為每個Li
都新增事件,則會造成dom
訪問次數過多,引起瀏覽器重繪與重排的次數過多,效能則會降低。 使用事件委託則可以解決這樣的問題
原理
實現事件委託是利用了事件的冒泡原理實現的。當我們為最外層的節點新增點選事件,那麼裡面的
ul
、li
、a
的點選事件都會冒泡到最外層節點上,委託它代為執行事件
```html
- 1
- 2
- 3
```
DNS 協議是什麼
概念: DNS 是域名系統 (Domain Name System) 的縮寫,提供的是一種主機名到 IP 地址的轉換服務,就是我們常說的域名系統。它是一個由分層的 DNS 伺服器組成的分散式資料庫,是定義了主機如何查詢這個分散式資料庫的方式的應用層協議。能夠使人更方便的訪問網際網路,而不用去記住能夠被機器直接讀取的IP數串。
作用: 將域名解析為IP地址,客戶端向DNS伺服器(DNS伺服器有自己的IP地址)傳送域名查詢請求,DNS伺服器告知客戶機Web伺服器的 IP 地址。
OSI七層模型
ISO
為了更好的使網路應用更為普及,推出了OSI
參考模型。
(1)應用層
OSI
參考模型中最靠近使用者的一層,是為計算機使用者提供應用介面,也為使用者直接提供各種網路服務。我們常見應用層的網路服務協議有:HTTP
,HTTPS
,FTP
,POP3
、SMTP
等。
- 在客戶端與伺服器中經常會有資料的請求,這個時候就是會用到
http(hyper text transfer protocol)(超文字傳輸協議)
或者https
.在後端設計資料介面時,我們常常使用到這個協議。 FTP
是檔案傳輸協議,在開發過程中,個人並沒有涉及到,但是我想,在一些資源網站,比如百度網盤``迅雷
應該是基於此協議的。SMTP
是simple mail transfer protocol(簡單郵件傳輸協議)
。在一個專案中,在使用者郵箱驗證碼登入的功能時,使用到了這個協議。
(2)表示層
表示層提供各種用於應用層資料的編碼和轉換功能,確保一個系統的應用層傳送的資料能被另一個系統的應用層識別。如果必要,該層可提供一種標準表示形式,用於將計算機內部的多種資料格式轉換成通訊中採用的標準表示形式。資料壓縮和加密也是表示層可提供的轉換功能之一。
在專案開發中,為了方便資料傳輸,可以使用base64
對資料進行編解碼。如果按功能來劃分,base64
應該是工作在表示層。
(3)會話層
會話層就是負責建立、管理和終止表示層實體之間的通訊會話。該層的通訊由不同裝置中的應用程式之間的服務請求和響應組成。
(4)傳輸層
傳輸層建立了主機端到端的連結,傳輸層的作用是為上層協議提供端到端的可靠和透明的資料傳輸服務,包括處理差錯控制和流量控制等問題。該層向高層遮蔽了下層資料通訊的細節,使高層使用者看到的只是在兩個傳輸實體間的一條主機到主機的、可由使用者控制和設定的、可靠的資料通路。我們通常說的,TCP
UDP
就是在這一層。埠號既是這裡的“端”。
(5)網路層
本層通過IP
定址來建立兩個節點之間的連線,為源端的運輸層送來的分組,選擇合適的路由和交換節點,正確無誤地按照地址傳送給目的端的運輸層。就是通常說的IP
層。這一層就是我們經常說的IP
協議層。IP
協議是Internet
的基礎。我們可以這樣理解,網路層規定了資料包的傳輸路線,而傳輸層則規定了資料包的傳輸方式。
(6)資料鏈路層
將位元組合成位元組,再將位元組組合成幀,使用鏈路層地址 (乙太網使用MAC地址)來訪問介質,並進行差錯檢測。 網路層與資料鏈路層的對比,通過上面的描述,我們或許可以這樣理解,網路層是規劃了資料包的傳輸路線,而資料鏈路層就是傳輸路線。不過,在資料鏈路層上還增加了差錯控制的功能。
(7)物理層
實際最終訊號的傳輸是通過物理層實現的。通過物理介質傳輸位元流。規定了電平、速度和電纜針腳。常用裝置有(各種物理裝置)集線器、中繼器、調變解調器、網線、雙絞線、同軸電纜。這些都是物理層的傳輸介質。
OSI七層模型通訊特點:對等通訊 對等通訊,為了使資料分組從源傳送到目的地,源端OSI模型的每一層都必須與目的端的對等層進行通訊,這種通訊方式稱為對等層通訊。在每一層通訊過程中,使用本層自己協議進行通訊。
參考 前端進階面試題詳細解答
HTTP狀態碼
狀態碼的類別:
| 類別 | 原因 | 描述 | | ------ | ----------------------- | ------------- | | 1xx | Informational(資訊性狀態碼) | 接受的請求正在處理 | | 2xx | Success(成功狀態碼) | 請求正常處理完畢 | | 3xx | Redirection(重定向狀態碼) | 需要進行附加操作一完成請求 | | 4xx | Client Error (客戶端錯誤狀態碼) | 伺服器無法處理請求 | | 5xx | Server Error(伺服器錯誤狀態碼) | 伺服器處理請求出錯 |
1. 2XX (Success 成功狀態碼)
狀態碼2XX表示請求被正常處理了。
(1)200 OK
200 OK表示客戶端發來的請求被伺服器端正常處理了。
(2)204 No Content
該狀態碼錶示客戶端傳送的請求已經在伺服器端正常處理了,但是沒有返回的內容,響應報文中不包含實體的主體部分。一般在只需要從客戶端往伺服器端傳送資訊,而伺服器端不需要往客戶端傳送內容時使用。
(3)206 Partial Content
該狀態碼錶示客戶端進行了範圍請求,而伺服器端執行了這部分的 GET 請求。響應報文中包含由 Content-Range 指定範圍的實體內容。
2. 3XX (Redirection 重定向狀態碼)
3XX 響應結果表明瀏覽器需要執行某些特殊的處理以正確處理請求。
(1)301 Moved Permanently
永久重定向。 該狀態碼錶示請求的資源已經被分配了新的 URI,以後應使用資源指定的 URI。新的 URI 會在 HTTP 響應頭中的 Location 首部欄位指定。若使用者已經把原來的URI儲存為書籤,此時會按照 Location 中新的URI重新儲存該書籤。同時,搜尋引擎在抓取新內容的同時也將舊的網址替換為重定向之後的網址。
使用場景:
- 當我們想換個域名,舊的域名不再使用時,使用者訪問舊域名時用301就重定向到新的域名。其實也是告訴搜尋引擎收錄的域名需要對新的域名進行收錄。
- 在搜尋引擎的搜尋結果中出現了不帶www的域名,而帶www的域名卻沒有收錄,這個時候可以用301重定向來告訴搜尋引擎我們目標的域名是哪一個。
(2)302 Found
臨時重定向。 該狀態碼錶示請求的資源被分配到了新的 URI,希望使用者(本次)能使用新的 URI 訪問資源。和 301 Moved Permanently 狀態碼相似,但是 302 代表的資源不是被永久重定向,只是臨時性質的。也就是說已移動的資源對應的 URI 將來還有可能發生改變。若使用者把 URI 儲存成書籤,但不會像 301 狀態碼出現時那樣去更新書籤,而是仍舊保留返回 302 狀態碼的頁面對應的 URI。同時,搜尋引擎會抓取新的內容而保留舊的網址。因為伺服器返回302程式碼,搜尋引擎認為新的網址只是暫時的。
使用場景:
- 當我們在做活動時,登入到首頁自動重定向,進入活動頁面。
- 未登陸的使用者訪問使用者中心重定向到登入頁面。
- 訪問404頁面重新定向到首頁。
(3)303 See Other
該狀態碼錶示由於請求對應的資源存在著另一個 URI,應使用 GET 方法定向獲取請求的資源。 303 狀態碼和 302 Found 狀態碼有著相似的功能,但是 303 狀態碼明確表示客戶端應當採用 GET 方法獲取資源。
303 狀態碼通常作為 PUT 或 POST 操作的返回結果,它表示重定向連結指向的不是新上傳的資源,而是另外一個頁面,比如訊息確認頁面或上傳進度頁面。而請求重定向頁面的方法要總是使用 GET。
注意:
- 當 301、302、303 響應狀態碼返回時,幾乎所有的瀏覽器都會把 POST 改成GET,並刪除請求報文內的主體,之後請求會再次自動傳送。
- 301、302 標準是禁止將 POST 方法變成 GET方法的,但實際大家都會這麼做。
(4)304 Not Modified
瀏覽器快取相關。 該狀態碼錶示客戶端傳送附帶條件的請求時,伺服器端允許請求訪問資源,但未滿足條件的情況。304 狀態碼返回時,不包含任何響應的主體部分。304 雖然被劃分在 3XX 類別中,但是和重定向沒有關係。
帶條件的請求(Http 條件請求):使用 Get方法 請求,請求報文中包含(if-match
、if-none-match
、if-modified-since
、if-unmodified-since
、if-range
)中任意首部。
狀態碼304並不是一種錯誤,而是告訴客戶端有快取,直接使用快取中的資料。返回頁面的只有頭部資訊,是沒有內容部分的,這樣在一定程度上提高了網頁的效能。
(5)307 Temporary Redirect
307表示臨時重定向。 該狀態碼與 302 Found 有著相同含義,儘管 302 標準禁止 POST 變成 GET,但是實際使用時還是這樣做了。
307 會遵守瀏覽器標準,不會從 POST 變成 GET。但是對於處理請求的行為時,不同瀏覽器還是會出現不同的情況。規範要求瀏覽器繼續向 Location 的地址 POST 內容。規範要求瀏覽器繼續向 Location 的地址 POST 內容。
3. 4XX (Client Error 客戶端錯誤狀態碼)
4XX 的響應結果表明客戶端是發生錯誤的原因所在。
(1)400 Bad Request
該狀態碼錶示請求報文中存在語法錯誤。當錯誤發生時,需修改請求的內容後再次傳送請求。另外,瀏覽器會像 200 OK 一樣對待該狀態碼。
(2)401 Unauthorized
該狀態碼錶示傳送的請求需要有通過 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
該狀態碼錶明請求資源的訪問被伺服器拒絕了,伺服器端沒有必要給出詳細理由,但是可以在響應報文實體的主體中進行說明。進入該狀態後,不能再繼續進行驗證。該訪問是永久禁止的,並且與應用邏輯密切相關。
IIS 定義了許多不同的 403 錯誤,它們指明更為具體的錯誤原因:
- 403.1 - 執行訪問被禁止。
- 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 - 在當前的應用程式池中不能執行所請求的 URL。這個錯誤程式碼為 IIS 6.0 所專用。
- 403.19 - 不能為這個應用程式池中的客戶端執行 CGI。這個錯誤程式碼為 IIS 6.0 所專用。
- 403.20 - Passport 登入失敗。這個錯誤程式碼為 IIS 6.0 所專用。
(4)404 Not Found
該狀態碼錶明伺服器上無法找到請求的資源。除此之外,也可以在伺服器端拒絕請求且不想說明理由時使用。 以下情況會出現404:
- 404.0 -(無) – 沒有找到檔案或目錄。
- 404.1 - 無法在所請求的埠上訪問 Web 站點。
- 404.2 - Web 服務擴充套件鎖定策略阻止本請求。
- 404.3 - MIME 對映策略阻止本請求。
(5)405 Method Not Allowed
該狀態碼錶示客戶端請求的方法雖然能被伺服器識別,但是伺服器禁止使用該方法。GET 和 HEAD 方法,伺服器應該總是允許客戶端進行訪問。客戶端可以通過 OPTIONS 方法(預檢)來檢視伺服器允許的訪問方法, 如下
```javascript Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE
```
4. 5XX (Server Error 伺服器錯誤狀態碼)
5XX 的響應結果表明伺服器本身發生錯誤.
(1)500 Internal Server Error
該狀態碼錶明伺服器端在執行請求時發生了錯誤。也有可能是 Web 應用存在的 bug 或某些臨時的故障。
(2)502 Bad Gateway
該狀態碼錶明扮演閘道器或代理角色的伺服器,從上游伺服器中接收到的響應是無效的。注意,502 錯誤通常不是客戶端能夠修復的,而是需要由途經的 Web 伺服器或者代理伺服器對其進行修復。以下情況會出現502:
- 502.1 - CGI (通用閘道器介面)應用程式超時。
- 502.2 - CGI (通用閘道器介面)應用程式出錯。
(3)503 Service Unavailable
該狀態碼錶明伺服器暫時處於超負載或正在進行停機維護,現在無法處理請求。如果事先得知解除以上狀況需要的時間,最好寫入 RetryAfter 首部欄位再返回給客戶端。
使用場景:
- 伺服器停機維護時,主動用503響應請求;
- nginx 設定限速,超過限速,會返回503。
(4)504 Gateway Timeout
該狀態碼錶示閘道器或者代理的伺服器無法在規定的時間內獲得想要的響應。他是HTTP 1.1中新加入的。
使用場景:程式碼執行時間超時,或者發生了死迴圈。
5. 總結
(1)2XX 成功
- 200 OK,表示從客戶端發來的請求在伺服器端被正確處理
- 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,表示伺服器允許訪問資源,但因發生請求未滿足條件的情況
- 307 temporary redirect,臨時重定向,和302含義類似,但是期望客戶端保持請求方法不變向新的地址發出請求
(3)4XX 客戶端錯誤
- 400 bad request,請求報文存在語法錯誤
- 401 unauthorized,表示傳送的請求需要有通過 HTTP 認證的認證資訊
- 403 forbidden,表示對請求資源的訪問被伺服器拒絕
- 404 not found,表示在伺服器上沒有找到請求的資源
(4)5XX 伺服器錯誤
- 500 internal sever error,表示伺服器端在執行請求時發生了錯誤
- 501 Not Implemented,表示伺服器不支援當前請求所需要的某個功能
- 503 service unavailable,表明伺服器暫時處於超負載或正在停機維護,無法處理請求
This
不同情況的呼叫,
this
指向分別如何。順帶可以提一下es6
中箭頭函式沒有this
,arguments
,super
等,這些只依賴包含箭頭函式最接近的函式我們先來看幾個函式呼叫的場景
```javascript function foo() { console.log(this.a) } var a = 1 foo()
const obj = { a: 2, foo: foo } obj.foo()
const c = new foo() ```
- 對於直接呼叫
foo
來說,不管foo
函式被放在了什麼地方,this
一定是window
- 對於
obj.foo()
來說,我們只需要記住,誰呼叫了函式,誰就是this
,所以在這個場景下foo
函式中的this
就是obj
物件 - 對於
new
的方式來說,this
被永遠繫結在了c
上面,不會被任何方式改變this
說完了以上幾種情況,其實很多程式碼中的
this
應該就沒什麼問題了,下面讓我們看看箭頭函式中的this
javascript
function a() {
return () => {
return () => {
console.log(this)
}
}
}
console.log(a()()())
- 首先箭頭函式其實是沒有
this
的,箭頭函式中的this
只取決包裹箭頭函式的第一個普通函式的this
。在這個例子中,因為包裹箭頭函式的第一個普通函式是a
,所以此時的this
是window
。另外對箭頭函式使用bind
這類函式是無效的。 - 最後種情況也就是
bind
這些改變上下文的API
了,對於這些函式來說,this
取決於第一個引數,如果第一個引數為空,那麼就是window
。 - 那麼說到
bind
,不知道大家是否考慮過,如果對一個函式進行多次bind
,那麼上下文會是什麼呢?
javascript
let a = {}
let fn = function () { console.log(this) }
fn.bind().bind(a)() // => ?
如果你認為輸出結果是
a
,那麼你就錯了,其實我們可以把上述程式碼轉換成另一種形式
javascript
// fn.bind().bind(a) 等於
let fn2 = function fn1() {
return function() {
return fn.apply()
}.apply(a)
}
fn2()
可以從上述程式碼中發現,不管我們給函式
bind
幾次,fn
中的this
永遠由第一次bind
決定,所以結果永遠是window
javascript
let a = { name: 'poetries' }
function foo() {
console.log(this.name)
}
foo.bind(a)() // => 'poetries'
以上就是
this
的規則了,但是可能會發生多個規則同時出現的情況,這時候不同的規則之間會根據優先順序最高的來決定this
最終指向哪裡。首先,
new
的方式優先順序最高,接下來是bind
這些函式,然後是obj.foo()
這種呼叫方式,最後是foo
這種呼叫方式,同時,箭頭函式的this
一旦被繫結,就不會再被任何方式所改變。
函式執行改變this
- 由於 JS 的設計原理: 在函式中,可以引用執行環境中的變數。因此就需要一個機制來讓我們可以在函式體內部獲取當前的執行環境,這便是
this
。
因此要明白
this
指向,其實就是要搞清楚 函式的執行環境,說人話就是,誰呼叫了函式。例如
obj.fn()
,便是obj
呼叫了函式,既函式中的this === obj
fn()
,這裡可以看成window.fn()
,因此this === window
但這種機制並不完全能滿足我們的業務需求,因此提供了三種方式可以手動修改
this
的指向:
call: fn.call(target, 1, 2)
apply: fn.apply(target, [1, 2])
bind: fn.bind(target)(1,2)
TCP的流量控制機制
一般來說,流量控制就是為了讓傳送方傳送資料的速度不要太快,要讓接收方來得及接收。TCP採用大小可變的滑動視窗進行流量控制,視窗大小的單位是位元組。這裡說的視窗大小其實就是每次傳輸的資料大小。
- 當一個連線建立時,連線的每一端分配一個緩衝區來儲存輸入的資料,並將緩衝區的大小發送給另一端。
- 當資料到達時,接收方傳送確認,其中包含了自己剩餘的緩衝區大小。(剩餘的緩衝區空間的大小被稱為視窗,指出視窗大小的通知稱為視窗通告 。接收方在傳送的每一確認中都含有一個視窗通告。)
- 如果接收方應用程式讀資料的速度能夠與資料到達的速度一樣快,接收方將在每一確認中傳送一個正的視窗通告。
- 如果傳送方操作的速度快於接收方,接收到的資料最終將充滿接收方的緩衝區,導致接收方通告一個零視窗 。傳送方收到一個零視窗通告時,必須停止傳送,直到接收方重新通告一個正的視窗。
TCP和UDP的區別
| | UDP | TCP | | ------ | --------------------- | -------------------------- | | 是否連線 | 無連線 | 面向連線 | | 是否可靠 | 不可靠傳輸,不使用流量控制和擁塞控制 | 可靠傳輸(資料順序和正確性),使用流量控制和擁塞控制 | | 連線物件個數 | 支援一對一,一對多,多對一和多對多互動通訊 | 只能是一對一通訊 | | 傳輸方式 | 面向報文 | 面向位元組流 | | 首部開銷 | 首部開銷小,僅8位元組 | 首部最小20位元組,最大60位元組 | | 適用場景 | 適用於實時應用,例如視訊會議、直播 | 適用於要求可靠傳輸的應用,例如檔案傳輸 |
模組化
js 中現在比較成熟的有四種模組載入方案:
- 第一種是 CommonJS 方案,它通過 require 來引入模組,通過 module.exports 定義模組的輸出介面。這種模組載入方案是伺服器端的解決方案,它是以同步的方式來引入模組的,因為在服務端檔案都儲存在本地磁碟,所以讀取非常快,所以以同步的方式載入沒有問題。但如果是在瀏覽器端,由於模組的載入是使用網路請求,因此使用非同步載入的方式更加合適。
- 第二種是 AMD 方案,這種方案採用非同步載入的方式來載入模組,模組的載入不影響後面語句的執行,所有依賴這個模組的語句都定義在一個回撥函式裡,等到載入完成後再執行回撥函式。require.js 實現了 AMD 規範
- 第三種是 CMD 方案,這種方案和 AMD 方案都是為了解決非同步模組載入的問題,sea.js 實現了 CMD 規範。它和require.js的區別在於模組定義時對依賴的處理不同和對依賴模組的執行時機的處理不同。
- 第四種方案是 ES6 提出的方案,使用 import 和 export 的形式來匯入匯出模組
在有
Babel
的情況下,我們可以直接使用ES6
的模組化
```javascript // file a.js export function a() {} export function b() {} // file b.js export default function() {}
import {a, b} from './a.js' import XXX from './b.js' ```
CommonJS
CommonJs
是Node
獨有的規範,瀏覽器中使用就需要用到Browserify
解析了。
```javascript // a.js module.exports = { a: 1 } // or exports.a = 1
// b.js var module = require('./a.js') module.a // -> log 1 ```
在上述程式碼中,
module.exports
和exports
很容易混淆,讓我們來看看大致內部實現
javascript
var module = require('./a.js')
module.a
// 這裡其實就是包裝了一層立即執行函式,這樣就不會汙染全域性變量了,
// 重要的是 module 這裡,module 是 Node 獨有的一個變數
module.exports = {
a: 1
}
// 基本實現
var module = {
exports: {} // exports 就是個空物件
}
// 這個是為什麼 exports 和 module.exports 用法相似的原因
var exports = module.exports
var load = function (module) {
// 匯出的東西
var a = 1
module.exports = a
return module.exports
};
再來說說
module.exports
和exports
,用法其實是相似的,但是不能對exports
直接賦值,不會有任何效果。對於
CommonJS
和ES6
中的模組化的兩者區別是:
- 前者支援動態匯入,也就是
require(${path}/xx.js)
,後者目前不支援,但是已有提案,前者是同步匯入,因為用於服務端,檔案都在本地,同步匯入即使卡住主執行緒影響也不大。 - 而後者是非同步匯入,因為用於瀏覽器,需要下載檔案,如果也採用同步匯入會對渲染有很大影響
- 前者在匯出時都是值拷貝,就算匯出的值變了,匯入的值也不會改變,所以如果想更新值,必須重新匯入一次。
- 但是後者採用實時繫結的方式,匯入匯出的值都指向同一個記憶體地址,所以匯入值會跟隨匯出值變化
- 後者會編譯成
require/exports
來執行的
AMD
AMD
是由RequireJS
提出的
AMD 和 CMD 規範的區別?
- 第一個方面是在模組定義時對依賴的處理不同。AMD推崇依賴前置,在定義模組的時候就要宣告其依賴的模組。而 CMD 推崇就近依賴,只有在用到某個模組的時候再去 require。
- 第二個方面是對依賴模組的執行時機處理不同。首先 AMD 和 CMD 對於模組的載入方式都是非同步載入,不過它們的區別在於模組的執行時機,AMD 在依賴模組載入完成後就直接執行依賴模組,依賴模組的執行順序和我們書寫的順序不一定一致。而 CMD在依賴模組載入完成後並不執行,只是下載而已,等到所有的依賴模組都載入好後,進入回撥函式邏輯,遇到 require 語句的時候才執行對應的模組,這樣模組的執行順序就和我們書寫的順序保持一致了。
```javascript // CMD define(function(require, exports, module) { var a = require("./a"); a.doSomething(); // 此處略去 100 行 var b = require("./b"); // 依賴可以就近書寫 b.doSomething(); // ... });
// AMD 預設推薦 define(["./a", "./b"], function(a, b) { // 依賴必須一開始就寫好 a.doSomething(); // 此處略去 100 行 b.doSomething(); // ... }) ```
- AMD :
requirejs
在推廣過程中對模組定義的規範化產出,提前執行,推崇依賴前置 - CMD :
seajs
在推廣過程中對模組定義的規範化產出,延遲執行,推崇依賴就近 - CommonJs :模組輸出的是一個值的
copy
,執行時載入,載入的是一個物件(module.exports
屬性),該物件只有在指令碼執行完才會生成 - ES6 Module :模組輸出的是一個值的引用,編譯時輸出介面,
ES6
模組不是物件,它對外介面只是一種靜態定義,在程式碼靜態解析階段就會生成。
談談對模組化開發的理解
- 我對模組的理解是,一個模組是實現一個特定功能的一組方法。在最開始的時候,js 只實現一些簡單的功能,所以並沒有模組的概念,但隨著程式越來越複雜,程式碼的模組化開發變得越來越重要。
- 由於函式具有獨立作用域的特點,最原始的寫法是使用函式來作為模組,幾個函式作為一個模組,但是這種方式容易造成全域性變數的汙染,並且模組間沒有聯絡。
- 後面提出了物件寫法,通過將函式作為一個物件的方法來實現,這樣解決了直接使用函式作為模組的一些缺點,但是這種辦法會暴露所有的所有的模組成員,外部程式碼可以修改內部屬性的值。
- 現在最常用的是立即執行函式的寫法,通過利用閉包來實現模組私有作用域的建立,同時不會對全域性作用域造成汙染。
for...in和for...of的區別
for…of 是ES6新增的遍歷方式,允許遍歷一個含有iterator介面的資料結構(陣列、物件等)並且返回各項的值,和ES3中的for…in的區別如下
- for…of 遍歷獲取的是物件的鍵值,for…in 獲取的是物件的鍵名;
- for… in 會遍歷物件的整個原型鏈,效能非常差不推薦使用,而 for … of 只遍歷當前物件不會遍歷原型鏈;
- 對於陣列的遍歷,for…in 會返回陣列中所有可列舉的屬性(包括原型鏈上可列舉的屬性),for…of 只返回陣列的下標對應的屬性值;
總結: for...in 迴圈主要是為了遍歷物件而生,不適用於遍歷陣列;for...of 迴圈可以用來遍歷陣列、類陣列物件,字串、Set、Map 以及 Generator 物件。
HTTP2的頭部壓縮演算法是怎樣的?
HTTP2的頭部壓縮是HPACK演算法。在客戶端和伺服器兩端建立“字典”,用索引號表示重複的字串,採用哈夫曼編碼來壓縮整數和字串,可以達到50%~90%的高壓縮率。
具體來說:
- 在客戶端和伺服器端使用“首部表”來跟蹤和儲存之前傳送的鍵值對,對於相同的資料,不再通過每次請求和響應傳送;
- 首部表在HTTP/2的連線存續期內始終存在,由客戶端和伺服器共同漸進地更新;
- 每個新的首部鍵值對要麼被追加到當前表的末尾,要麼替換表中之前的值。
例如下圖中的兩個請求, 請求一發送了所有的頭部欄位,第二個請求則只需要傳送差異資料,這樣可以減少冗餘資料,降低開銷。
陣列能夠呼叫的函式有那些?
- push
- pop
- splice
- slice
- shift
- unshift
- sort
- find
- findIndex
- map/filter/reduce 等函數語言程式設計方法
- 還有一些原型鏈上的方法:toString/valudOf
原型鏈指向
```javascript 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
```
PWA使用過嗎?serviceWorker的使用原理是啥?
漸進式網路應用(PWA)
是谷歌在2015年底提出的概念。基本上算是web應用程式,但在外觀和感覺上與原生app
類似。支援PWA
的網站可以提供離線工作、推送通知和裝置硬體訪問等功能。
Service Worker
是瀏覽器在後臺獨立於網頁執行的指令碼,它打開了通向不需要網頁或使用者互動的功能的大門。 現在,它們已包括如推送通知和後臺同步等功能。 將來,Service Worker
將會支援如定期同步或地理圍欄等其他功能。 本教程討論的核心功能是攔截和處理網路請求,包括通過程式來管理快取中的響應。
程式碼輸出結果
```javascript var F = function() {}; Object.prototype.a = function() { console.log('a'); }; Function.prototype.b = function() { console.log('b'); } var f = new F(); f.a(); f.b(); F.a(); F.b()
```
輸出結果:
```javascript a Uncaught TypeError: f.b is not a function a b
```
解析:
- f 並不是 Function 的例項,因為它本來就不是建構函式,呼叫的是 Function 原型鏈上的相關屬性和方法,只能訪問到 Object 原型鏈。所以 f.a() 輸出 a ,而 f.b() 就報錯了。
- F 是個建構函式,而 F 是建構函式 Function 的一個例項。因為 F instanceof Object === true,F instanceof Function === true,由此可以得出結論:F 是 Object 和 Function 兩個的例項,即 F 能訪問到 a, 也能訪問到 b。所以 F.a() 輸出 a ,F.b() 輸出 b。
Proxy 可以實現什麼功能?
在 Vue3.0 中通過 Proxy
來替換原本的 Object.defineProperty
來實現資料響應式。
Proxy 是 ES6 中新增的功能,它可以用來自定義物件中的操作。
```javascript let p = new Proxy(target, handler)
```
target
代表需要新增代理的物件,handler
用來自定義物件中的操作,比如可以用來自定義 set
或者 get
函式。
下面來通過 Proxy
來實現一個數據響應式:
``javascript
let onWatch = (obj, setBind, getLogger) => {
let handler = {
get(target, property, receiver) {
getLogger(target, property)
return Reflect.get(target, property, receiver)
},
set(target, property, value, receiver) {
setBind(value, property)
return Reflect.set(target, property, value)
}
}
return new Proxy(obj, handler)
}
let obj = { a: 1 }
let p = onWatch(
obj,
(v, property) => {
console.log(
監聽到屬性${property}改變為${v})
},
(target, property) => {
console.log(
'${property}' = ${target[property]}`)
}
)
p.a = 2 // 監聽到屬性a改變
p.a // 'a' = 2
```
在上述程式碼中,通過自定義 set
和 get
函式的方式,在原本的邏輯中插入了我們的函式邏輯,實現了在對物件任何屬性進行讀寫時發出通知。
當然這是簡單版的響應式實現,如果需要實現一個 Vue 中的響應式,需要在 get
中收集依賴,在 set
派發更新,之所以 Vue3.0 要使用 Proxy
替換原本的 API 原因在於 Proxy
無需一層層遞迴為每個屬性新增代理,一次即可完成以上操作,效能上更好,並且原本的實現有一些資料更新不能監聽到,但是 Proxy
可以完美監聽到任何方式的資料改變,唯一缺陷就是瀏覽器的相容性不好。
手寫 bind、apply、call
```javascript // call
Function.prototype.call = function (context, ...args) { context = context || window;
const fnSymbol = Symbol("fn"); context[fnSymbol] = this;
contextfnSymbol; delete context[fnSymbol]; }
```
```javascript // apply
Function.prototype.apply = function (context, argsArr) { context = context || window;
const fnSymbol = Symbol("fn"); context[fnSymbol] = this;
contextfnSymbol; delete context[fnSymbol]; }
```
```javascript // bind
Function.prototype.bind = function (context, ...args) { context = context || window; const fnSymbol = Symbol("fn"); context[fnSymbol] = this;
return function (..._args) { args = args.concat(_args);
context[fnSymbol](...args);
delete context[fnSymbol];
} }
```
同樣是重定向,307,303,302的區別?
302是http1.0的協議狀態碼,在http1.1版本的時候為了細化302狀態碼⼜出來了兩個303和307。 303明確表示客戶端應當採⽤get⽅法獲取資源,他會把POST請求變為GET請求進⾏重定向。 307會遵照瀏覽器標準,不會從post變為get。