年前面試據說是一年中最好通過面試的時候,這個時候面試的人少,加之崗位急,供需關係決定比以往更容易拿一個不錯的工資。
趁著這幾天結束了幾月的旅行,在家沒事,恰好有充分的時間,面了幾家大廠。最終也有幾家拿了 Offer,再接再厲,最近有面試的同學也可以與我交流。
總結題目如下(連結在左下角原文打開)
01 如何實現一個元素的水平垂直居中在 Issue 或者我的網站中交流與討論: 01 如何實現一個元素的水平垂直居中
提供一個較少提過的方法,使用 grid,它是做二維布局的,但是只有一個子元素時,一維布局與二維布局就一樣了。結合 justify-content/justify-items 和 align-content/align-items 就有四種方案
效果可以見 codepen
.container {
display: grid;
justify-content: center;
align-content: center;
}.container {
display: grid;
justify-content: center;
align-items: center;
}.container {
display: grid;
justify-items: center;
align-content: center;
}.container {
display: grid;
justify-items: center;
align-items: center;
}
02 css 如何實現左側固定300px,右側自適應的布局在 Issue 或者我的網站中交流與討論: 02 css 如何實現左側固定300px,右側自適應的布局
使用 flex 布局,左側 300px,右側 flex-grow: 1。pug 代碼及 css 代碼示例如下
.container
.left
.main.container {
display: flex;
}
.left {
flex-basis: 300px;
}
.main {
flex-grow: 1;
}此處看起來比較圓滿了,其實還有一個缺陷: 如果 .main 區域過大擠壓 .left 區域怎麼辦,此時還需要加一個禁止擠壓
.left {
flex-basis: 300px;
flex-shrink: 0;
}
總結使用 flex 進行如下布局
.container
.left
.main.container {
display: flex;
}
.left {
flex-basis: 300px;
flex-shrink: 0;
}
.main {
flex-grow: 1;
}
03 http 狀態碼 502 和 504 有什麼區別在 Issue 或者我的網站中交流與討論: 03 http 狀態碼 502 和 504 有什麼區別
這兩種異常狀態碼都與網關 Gateway 有關,首先明確兩個概念
Proxy (Gateway),反向代理層或者網關層。在公司級應用中一般使用 Nginx 扮演這個角色Application (upstream serrver),應用層服務,作為 Proxy 層的上遊服務。在公司中一般為各種語言編寫的伺服器應用,如 Go/Java/Python/PHP/Node 等此時關於 502 與 504 的區別就很顯而易見
502 Bad Gateway。一般表現為你自己寫的應用層服務(Java/Go/PHP)掛了,網關層無法接收到響應504 Gateway Timeout。一般表現為應用層服務 (upstream) 超時,如查庫操作耗時十分鐘,超過了 Nginx 配置的超時時間 04 如何使用 react hooks 實現 useFetch 請求數據 更多描述: 比如設計成 `useFetch` 這種形式,它的 API 應該如何設計在 Issue 或者我的網站中交流與討論: 04 如何使用 react hooks 實現 useFetch 請求數據
可以參考 How to fetch data with React Hooks?
05 react 如何使用 render prop component 請求數據在 Issue 或者我的網站中交流與討論: 05 react 如何使用 render prop component 請求數據
參考: https://www.robinwieruch.de/react-fetching-data#how-to-fetch-data-in-render-props
06 什麼是 virtual DOM,它的引入帶了什麼好處在 Issue 或者我的網站中交流與討論: 06 什麼是 virtual DOM,它的引入帶了什麼好處
數據與UI的進一步分離,這樣也更有利於 SSR
07 http 服務中靜態文件的 Last-Modified 是根據什麼生成的在 Issue 或者我的網站中交流與討論: 07 http 服務中靜態文件的 Last-Modified 是根據什麼生成的
一般會選文件的 mtime,表示文件內容的修改時間
nginx 也是這樣處理的,源碼見: ngx_http_static_module.c
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = of.size;
r->headers_out.last_modified_time = of.mtime;關於為什麼使用 mtime 而非 ctime,可以參考 #116
08 localhost:3000 與 localhost:5000 的 cookie 信息是否共享在 Issue 或者我的網站中交流與討論: 08 localhost:3000 與 localhost:5000 的 cookie 信息是否共享
共享
09 http 響應頭中如果 content-type 為 application/octet-stream,則代表什麼意思在 Issue 或者我的網站中交流與討論: 09 http 響應頭中如果 content-type 為 application/octet-stream,則代表什麼意思
代表二進位流,一般用以下載文件
10 http 響應頭中的 Date 與 Last-Modified 有什麼不同,網站部署時需要注意什麼在 Issue 或者我的網站中交流與討論: 10 http 響應頭中的 Date 與 Last-Modified 有什麼不同,網站部署時需要注意什麼
Date: 報文在源伺服器的產生時間,由此可查看報文已緩存了多久時間Last-Modified: 源伺服器上資源的上次修改時間LM-Factor 與它倆有關。
簡而言之,一個靜態資源沒有設置 Cache-Control 時會以這兩個響應頭來設置強制緩存時間:(Date - LastModified) * n,而非直接進行協商緩存。在涉及到 CDN 時,表現更為明顯,體現在更新代碼部署後,界面沒有更新。
11 http 1.1 中的 keep-alive 有什麼作用在 Issue 或者我的網站中交流與討論: 11 http 1.1 中的 keep-alive 有什麼作用
在 http 1.1 中,在響應頭中設置 keep-alive 可以在一個 TCP 連接上發送多個 http 請求
在伺服器端使用響應頭開啟 keep-alive
Connection: Keep-Alive
Keep-Alive: timeout=5, max=1000
12 如果使用 SSR,可以在 created/componentWillMount 中訪問 localStorage 嗎在 Issue 或者我的網站中交流與討論: 12 如果使用 SSR,可以在 created/componentWillMount 中訪問 localStorage 嗎
不可以,created/componentWillMount 時,還未掛載,代碼仍然在伺服器中執行,此時沒有瀏覽器環境,因此此時訪問 localStorage 將會報錯
13 CSP 是幹什麼用的了在 Issue 或者我的網站中交流與討論: 13 CSP 是幹什麼用的了
CSP 只允許加載指定的腳本及樣式,最大限度地防止 XSS 攻擊,是解決 XSS 的最優解。CSP 的設置根據加載頁面時 http 的響應頭 Content Security Policy 在伺服器端控制。
外部腳本可以通過指定域名來限制:Content-Security-Policy: script-src 'self',self 代表只加載當前域名如果網站必須加載內聯腳本 (inline script) ,則可以提供一個 nonce 才能執行腳本,攻擊者則無法注入腳本進行攻擊。Content-Security-Policy: script-src 'nonce-xxxxxxxxxxxxxxxxxx'通過 devtools -> network 可見 github 的 CSP 配置如下:
Content-Security-Policy: default-src 'none';
base-uri 'self';
block-all-mixed-content;
connect-src 'self' uploads.github.com www.githubstatus.com collector.githubapp.com api.github.com www.google-analytics.com github-cloud.s3.amazonaws.com github-production-repository-file-5c1aeb.s3.amazonaws.com github-production-upload-manifest-file-7fdce7.s3.amazonaws.com github-production-user-asset-6210df.s3.amazonaws.com cdn.optimizely.com logx.optimizely.com/v1/events wss://alive.github.com;
font-src github.githubassets.com;
form-action 'self' github.com gist.github.com;
frame-ancestors 'none';
frame-src render.githubusercontent.com;
img-src 'self' data: github.githubassets.com identicons.github.com collector.githubapp.com github-cloud.s3.amazonaws.com *.githubusercontent.com;
manifest-src 'self';
media-src 'none';
script-src github.githubassets.com;
style-src 'unsafe-inline' github.githubassets.com;
worker-src github.com/socket-worker.js gist.github.com/socket-worker.js
相關連結Content Security Policy 入門教程 - 阮一峰Content Security Policy - w3 14 簡述下 css specificity在 Issue 或者我的網站中交流與討論: 14 簡述下 css specificity
css specificity 即 css 中關於選擇器的權重,以下三種類型的選擇器依次下降
class、attribute 與 pseudo-classes 選擇器,如 .header、[type="radio"] 與 :hovertype 標籤選擇器和偽元素選擇器,如 h1、p 和 ::before其中通配符選擇器 *,組合選擇器 + ~ >,否定偽類選擇器 :not() 對優先級無影響
另有內聯樣式 <div style="color: red;"></div> 及 !important(最高) 具有更高的權重
:not 的優先級影響 - codepen 可以看出 :not 對選擇器的優先級無任何影響
15 position: sticky 如何工作,適用於哪些場景在 Issue 或者我的網站中交流與討論: 15 position: sticky 如何工作,適用於哪些場景
position: sticky 可理解為 relative 與 fixed 的結合體
16 什麼情況下會發送 OPTIONS 請求在 Issue 或者我的網站中交流與討論: 16 什麼情況下會發送 OPTIONS 請求
當一個請求跨域且不是簡單請求時就會發送 OPTIONS 請求
滿足以下條件就是一個簡單請求:
Method: 請求的方法是 GET、POST 及 HEADHeader: 請求頭是 Content-Type、Accept-Language、Content-Language 等Content-Type: 請求類型是 application/x-www-form-urlencoded、multipart/form-data 或 text/plain而在項目中常見的 Content-Type: application/json 及 Authorization: <token> 為典型的非簡單請求,在發送請求時往往會帶上 Options
更詳細內容請參考 CORS - MDN
17 簡述下 TLS 握手過程在 Issue 或者我的網站中交流與討論: 17 簡述下 TLS 握手過程
TLS 握手的詳細過程可參考下圖:
TLS handshake以上圖片來自 high-performance-browser
從 wireshark 抓包,也可以看到握手的詳細流程,建議抓包加強理解,以下是抓包 https://q.shanyue.tech 時的握手流程
通過 curl -vvv --head 來查看握手信息:
$ curl -vvv --head https://q.shanyue.tech
* Trying 111.6.180.235...
* TCP_NODELAY set
* Connected to q.shanyue.tech (111.6.180.235) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
* CAfile: /etc/ssl/cert.pem
CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, server accepted to use h2
* Server certificate:
* subject: CN=q.shanyue.tech
* start date: Dec 2 00:00:00 2019 GMT
* expire date: Dec 1 12:00:00 2020 GMT
* subjectAltName: host "q.shanyue.tech" matched cert's "q.shanyue.tech"
* issuer: C=US; O=DigiCert Inc; OU=www.digicert.com; CN=Encryption Everywhere DV TLS CA - G1
* SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7f95ba80dc00)
握手過程在 TLS 1.2 中,握手協議過程需要耗費兩個 RTT,過程如下
[OUT] Client Hello,客戶端選出自身支持的 TLS 版本號、cipher suites、一個隨機數、SessionId 傳送給伺服器端 (有可能可服用 Session)[IN] Server Hello,伺服器端選出雙方都支持的 TLS 版本,cipher suite 、一個隨機數、SeesionId 給客戶端[IN] Certificate,伺服器端給客戶端發送證書,用以身份驗證及提供公鑰[IN] Server Key Exchange,伺服器端給客戶端發送秘鑰交換算法的一些參數[OUT] Client Key Exchange,客戶端給伺服器端發送秘鑰交換算法的一些參數,計算出預備主密鑰 (pre master key),作為隨機數傳遞給伺服器端 (這個隨機數是安全的)。雙方根據三個隨機數生成對稱加密中的秘鑰[OUT] Change Cipher Spec,告知對方以後的消息將要使用TLS記錄層協議進行加密[OUT] Finished,發送第一條加密的消息並完整性驗證[IN] Change Cipher Spec,告知以後的消息將要使用TLS記錄層協議進行加密[IN] Finished,發送第一條加密的消息並完整性驗證相關連結Chapter 4. Transport Layer Security (TLS) 18 TLS1.3 相比 TLS1.2 有何不同在 Issue 或者我的網站中交流與討論: 18 TLS1.3 相比 TLS1.2 有何不同
以下摘自 RFC 5246: TLS 1.2
Client Server
ClientHello --->
ServerHello
Certificate*
ServerKeyExchange*
CertificateRequest*
<--- ServerHelloDone
Certificate*
ClientKeyExchange
CertificateVerify*
[ChangeCipherSpec]
Finished --->
[ChangeCipherSpec]
<--- Finished
Application Data <--> Application Data
Figure 1. Message flow for a full handshake
* Indicates optional or situation-dependent messages that are not
always sent.以下摘自 RFC 8446: TLS 1.3
Client Server
Key ^ ClientHello
Exch | + key_share*
| + signature_algorithms*
| + psk_key_exchange_modes*
v + pre_shared_key* --->
ServerHello ^ Key
+ key_share* | Exch
+ pre_shared_key* v
{EncryptedExtensions} ^ Server
{CertificateRequest*} v Params
{Certificate*} ^
{CertificateVerify*} | Auth
{Finished} v
<--- [Application Data*]
^ {Certificate*}
Auth | {CertificateVerify*}
v {Finished} --->
[Application Data] <--> [Application Data]
+ Indicates noteworthy extensions sent in the
previously noted message.
* Indicates optional or situation-dependent
messages/extensions that are not always sent.
{} Indicates messages protected using keys
derived from a [sender]_handshake_traffic_secret.
[] Indicates messages protected using keys
derived from [sender]_application_traffic_secret_N.
Figure 1: Message Flow for Full TLS Handshake
握手時間從以前的 2RTT 縮短到 1RTT,通過 Pre shared-key 減少了單獨的 ServerKeyExchange 與 ClientKeyExchange 消耗的一個 RTT 19 你使用過哪些前端性能分析工具在 Issue 或者我的網站中交流與討論: 19 你使用過哪些前端性能分析工具
最常見且實用的性能工具有兩個:
lighthouse: 可在 chrome devtools 直接使用,根據個人設備及網絡對目標網站進行分析,並提供各種建議webpagetest: 分布式的性能分析工具,可在全球多個區域的伺服器資源為你的網站進行分析,並生成相應的報告 20 如何找到當前頁面出現次數最多的HTML標籤在 Issue 或者我的網站中交流與討論: 20 如何找到當前頁面出現次數最多的HTML標籤
這是一道前端基礎與編程功底具備的面試題:
如果你前端基礎強會了解 document.querySelector(*) 能夠列出頁面內所有標籤有三種 API 可以列出頁面所有標籤:
document.querySelector('*'),標準規範實現> document.querySelectorAll('*')
< NodeList(593) [html, head, meta, meta, meta, meta, meta, meta, meta, title, link#favicon, link, link#MainCss, link#mobile-style, link, link, link, script, script, script, script, script, script, script, link, script, link, link, script, input#_w_brink, body, a, div#home, div#header, div#blogTitle, a#lnkBlogLogo, img#blogLogo, h1, a#Header1_HeaderTitle.headermaintitle.HeaderMainTitle, h2, div#navigator, ul#navList, li, a#blog_nav_sitehome.menu, li, a#blog_nav_myhome.menu, li, a#blog_nav_newpost.menu, li, a#blog_nav_contact.menu, li, a#blog_nav_rss.menu, li, a#blog_nav_admin.menu, div.blogStats, span#stats_post_count, span#stats_article_count, span#stats-comment_count, div#main, div#mainContent, div.forFlow, div#post_detail, div#topics, div.post, h1.postTitle, a#cb_post_title_url.postTitle2.vertical-middle, span, div.clear, div.postBody, div#cnblogs_post_body.blogpost-body, p, p, strong, p, p, p, strong, div.cnblogs_code, pre, span, span, span, span, span, p, span, strong, pre, strong, span, strong, br, br, br, div.cnblogs_code, pre, span, span, p, p, …]
[0 … 99]
[100 … 199]
[200 … 299]
[300 … 399]
[400 … 499]
[500 … 592]
__proto__: NodeList使用 document.querySelectorAll 實現如下
const maxBy = (list, keyBy) => list.reduce((x, y) => keyBy(x) > keyBy(y) ? x : y)
function getFrequentTag () {
const tags = [...document.querySelectorAll('*')].map(x => x.tagName).reduce((o, tag) => {
o[tag] = o[tag] ? o[tag] + 1 : 1;
return o
}, {})
return maxBy(Object.entries(tags), tag => tag[1])
}使用 element.children 遞歸迭代如下 (最終結果多一個 document)
function getAllTags(el = document) {
const children = Array.from(el.children).reduce((x, y) => [...x, ...getAllTags(y)], [])
return children
}
// 或者通過 flatMap 實現
function getAllTags(el = document) {
const children = Array.prototype.flatMap.call(el.children, x => getAllTags(x))
return [el, ...children]
}
21 在 nginx 中如何配置負載均衡在 Issue 或者我的網站中交流與討論: 21 在 nginx 中如何配置負載均衡
通過 proxy_pass 與 upstream 即可實現最為簡單的負載均衡。如下配置會對流量均勻地導向 172.168.0.1,172.168.0.2 與 172.168.0.3 三個伺服器
http {
upstream backend {
server 172.168.0.1;
server 172.168.0.2;
server 172.168.0.3;
}
server {
listen 80;
location / {
proxy_pass http://backend;
}
}
}關於負載均衡的策略大致有以下四種種
weighted_round_robin,加權輪詢Round_Robin輪詢,nginx 默認的負載均衡策略就是輪詢,假設負載三臺伺服器節點為 A、B、C,則每次流量的負載結果為 ABCABC
Weighted_Round_Robin加權輪詢,根據關鍵字 weight 配置權重,如下則平均沒來四次請求,會有八次打在 A,會有一次打在 B,一次打在 C
upstream backend {
server 172.168.0.1 weight=8;
server 172.168.0.2 weight=1;
server 172.168.0.3 weight=1;
}
IP_hash對每次的 IP 地址進行 Hash,進而選擇合適的節點,如此,每次用戶的流量請求將會打在固定的伺服器上,利於緩存,也更利於 AB 測試等。
upstream backend {
server 172.168.0.1;
server 172.168.0.2;
server 172.168.0.3;
ip_hash;
}
Least Connection選擇連接數最少的伺服器節點優先負載
upstream backend {
server 172.168.0.1;
server 172.168.0.2;
server 172.168.0.3;
least_conn;
}說到最後,這些負載均衡策略對於應用開發者至關重要,而基礎開發者更看重如何實現這些策略,如這四種負載算法如何實現?請參考以後的文章
- END -