談面試前端工程師
談面試前端工程師
- 打戰需要周全的準備,而找一份年薪幾十萬的工作對於個人來說不亞於一場大戰
- 有一天,你若需要招人,則需要在極短的時間內從各方面考核對方是否合適
- 通過面試可以推動自己主動了解行業新技術,尤其是長期呆在一家技術不那麼好的公司
- 面試的內容數不勝數(亦或千奇百怪),面試官和面試者的背景也不盡相同,或許我們只能儘力而為。將每次面試當成一次修鍊,好好享受
以下是近期朋友去面試前端工程師所遇到的一些問題,整理如下:
vue
v-model 修飾符有哪些
答:.number、.trim、.lazy
Tip: 用法如下:
<input v-model.lazy="msg">
<input v-model.number="age" type="number">
<input v-model.trim="msg">
v-model 的原理
答:v-model 可以在表單或組件上創建雙向綁定。v-model 的本質其實是一個語法糖,即名為 value 的 props 以及 input 事件。
- 在組件上使用
<custom-input v-model="count"></custom-input>
等於
<custom-input :value="count" @input='changCount'></custom-input>
- 在表單上使用
<input v-model="message" placeholder="edit me">
等於
<input type="text" :value='message' @input='handleInput' placeholder="edit me">
Tip:更多介紹請看 v-model
v-if 與 v-for 一起使用
答:不推薦同時使用 v-if 和 v-for。若一定要一起使用,可以使用 template,就像這樣:
<li v-for="todo in todos" >
<template v-if="!todo.isComplete">
{{ todo }}
</template>
</li>
請簡單介紹下 vuex
答:在 vue 中我們可以使用 props 和 emit 解決父子組件之間的通訊,而非父子之間的通訊則可以使用 bus(中央事件匯流排)。而 Vuex 作為 vue 的一個插件,解決的問題與 bus 類似,更具體些,就是 vuex 能解決多個組件共享狀態的需求。
Vuex 的核心概念有 State、Getters、Mutations、Actions:
- state,即 vuex 的數據
- getters,可以認為是 store 的計算屬性
- mutations,更改 vuex 中 state(數據)的唯一方式
- actions,類似 mutation,但不能直接修改 state
Tip: 更多介紹請看 vuex基礎
vuex重刷還有嗎,如何做持久化
答:沒有了。可以配合 sessionStorage 做持久化,亦或使用 npm 包(vuex-persistedstate)。
Tip:
// App.vue
created(){
//頁面載入時讀取 localStorage 里的狀態資訊
if(localStorage.getItem("userMsg")){
this.$store.replaceState(...)
}
//在頁面刷新時保存到 localStorage
window.addEventListener("beforeunload",()=>{
localStorage.setItem(...)
})
}
vue-router 默認是什麼模式
答:hash
javascript
是否了解 es11,?.
和 ??
用過嗎
答:有所了解。例如 es11 中有 BigInt、可選鏈操作符( ?. )、空值合併操作符(??)。
?. 和 ?? 用法如下:
const obj = {
a: 1,
b: {
c: 2
}
};
// ?. 操作符的功能類似於 . 鏈式操作符,不同之處在於,
// 在引用為空 null 或者 undefined 的情況下不會引起錯誤,該表達式短路返回值是 undefined。
console.log(obj.d?.c) // undefined
console.log(obj.b?.c) // 2
console.log(obj?.someNonExistentMethod?.()) // undefined
// 當左側的操作數為 null 或者 undefined 時,返回其右側操作數,否則返回左側操作數
console.log(null ?? 'default string') // default string
// || vs ??
console.log(0 || 42) // 42
console.log(0 ?? 42) // 0
Tip:有關 ES2020、ES2021、ES2022 等特性可以查閱:kangax es2016plus、babel 插件列表。
什麼是事件循環
答:js 是單執行緒的語言,同一時刻只能執行也給任務,只有第一個任務完成,才能接著做第二個任務,如果上一個任務是計算量很大,cup 很忙導致無法接著做下一個任務,這種情況沒問題,但如果上一個任務是只是在等待(如 ajax 請求),cup 很閑而導致下一個任務也只能跟著一起等待,那麼就不好了,所以需要事件循環這個機制來解決此問題。
事件循環機制如下:
- 所有同步任務都在主執行緒上執行,形成一個
執行棧
- 除了主執行緒,還有一個
任務隊列
,只要非同步任務有了結果就會往任務隊列中放一個事件 - 當執行棧為空,
主執行緒
就會去讀任務隊列,放入執行棧中執行 - 如此循環
Tip:關於事件循環更詳細的介紹,有多種不同版本,筆者傾向於下面這種
主執行緒:
同步程式碼1->同步程式碼2->...->同步程式碼N
事件隊列:
- 宏任務隊列:事件1->事件2->...->事件N
- 微任務隊列: 事件1->事件2->...->事件N
①,執行棧中所有同步任務執行完畢
②,執行完微任務隊列中所有事件對應回調
③,取出宏任務隊列中一個事件,將對應回調加入執行棧
依次循環①②③①②③...
宏任務和微任務都有哪些
答:
- 微任務:Pormise.then、MutationObserver
- 宏任務:setTimeout、setInterval、requestAnimationFrame
給數組增加一個方法,只能存入唯一的值
答:
Array.prototype.uniquePush = function (...values) {
values.forEach(v => {
if (!this.includes(v)) {
this.push(v)
}
})
}
let array = [1, 2, 3]
array.uniquePush(4, 3)
// array: [ 1, 2, 3, 4 ]
console.log('array: ', array)
扁平化嵌套數組
例如有這麼一個數組:
const arr = [1, 2, [3, 4, 5, [6, 7, 8], 9], 10, [11, 12]]
將其扁平化(層級不限)成:
[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ]
可提供多種方式
答:
- 使用數組的 flat() 方法
const flated1 = arr.flat(Infinity)
console.log('flated1: ', flated1);
- 使用遞歸
function flat(arr, result = []) {
arr.forEach(item => {
let wrap = Array.isArray(item) ? flat(item) : [item]
result.push(...wrap)
})
return result
}
const flated2 = flat(arr)
console.log('flated2: ', flated2);
- 使用 toString() 方法
const flated3 = arr.toString().split(',').map(item => Number(item))
console.log('flated3: ', flated3)
Tip:Array.prototype.toString() 返回一個表示數組及其元素的字元串。並會調用每項的 toString() 方法:
let obj = {
toString() {
return 'i am obj'
}
}
let arr2 = ['a', 3, obj]
// a,3,i am obj
console.log(arr2.toString())
請完成 getTree() 方法
// 將數據轉成樹的結構
function getTree(arr) {
}
// parent 為 -1 的是根節點。只有一個根節點
let flatTree = [
{ name: 'b', id: 2, parent: 1 },
{ name: 'a', id: 1, parent: -1 },
{ name: 'c', id: 3, parent: 1 },
{ name: 'd', id: 4, parent: 3 },
{ name: 'e', id: 5, parent: 3 },
]
let tree = getTree(flatTree)
// {"name":"a","id":1,"parent":-1,"children":[{"name":"b","id":2,"parent":1},{"name":"c","id":3,"parent":1,"children":[{"name":"d","id":4,"parent":3},{"name":"e","id":5,"parent":3}]}]}
console.log(JSON.stringify(tree))
答:
function getTree(arr) {
arr.forEach(item => {
if (item.parent === -1) {
return
}
let pId = item.parent
let parentNode = arr.find(item => item.id === pId)
if (!parentNode.children) {
parentNode.children = []
}
parentNode.children.push(item)
})
const root = arr.find(item => item.parent === -1)
return root
}
刷新頁面 sesstionStorage 會清空嗎?
答:不會
刷新頁面,將在控制台依次輸出1、2、3…;若用新的 tab 訪問,又會依次輸出1、2、3…
let msg = sessionStorage.getItem('a')
if (!msg) {
sessionStorage.setItem('a', '1')
} else {
sessionStorage.setItem('a', (+msg) + 1)
}
console.log(sessionStorage.getItem('a'));
Tip: 更多介紹請看 sessionStorage
new Date(‘abc’) 會如何
答:控制台輸出 Invalid Date
async 中的 await 後面如果報錯,會發生什麼
答:會中斷 await 後續程式碼的執行,async 方法會立即結束。
Tip:
- 不明白題意,請看如下程式碼
async function foo() {
// obj 是一個不存在的變數
await obj
return 1
}
foo().then(v => {
console.log(v)
}).catch(v => {
console.log(`catch, ${v}`)
})
// 輸出:catch, ReferenceError: obj is not defined
- 為什麼會這樣可以查看:async
class 的構造函數能否使用 async
答:不能。
請看示例:
class Dog {
async constructor() {
this.name = 'apple'
}
async say() {
console.log(this.name);
}
}
new Dog().say()
// Class constructor may not be an async method
若用數組來實現棧、隊列,需要用到數組的什麼方法
答:棧是先進後出,所以會用到 push 、pop;而隊列是先進先出,故用到 push、shift
如何解決 0.1 + 0.2 != 0.3
答:此問題的出現是因為浮點數不能精確地用二進位表示所有小數,會出現一些意外的結果,比如:
let a = 0.1
let b = 0.2
let c = 0.3
// 0.30000000000000004
console.log(a + b)
有三種方法可以解決:
- Number.EPSILON
- 轉為整數再比較。即將數字提升 10 的 N 次方
- 使用 toFixed()
請看示例:
function equal(a, b) {
return Math.abs(a - b) < Number.EPSILON
}
let isEqual = equal(a + b, c)
// isEqual: true
console.log('isEqual: ', isEqual)
let seed = Math.pow(10, 10)
let isEqual2 = (a * seed + b * seed) === c * seed
// isEqual2: true
console.log('isEqual2: ', isEqual2);
let isEqual3 = parseFloat((a + b).toFixed(10)) === c
// isEqual3: true
console.log('isEqual3: ', isEqual3)
http
什麼是協商快取、什麼是強制快取
答:快取的作用很好理解,通過復用以前的資源,可以提高網站的性能,提升用戶體驗。
通常第一次獲取資源後,會根據返回的資訊(respone header)得知如何快取,例如做強快取、協商快取或不做快取。
強快取的特性是,下次直接從快取中讀取,不會發送到伺服器,返回的狀態碼是 200(from cache)
協商快取的特性,也是直接從快取中讀取,不過得先到伺服器那裡詢問一下快取資源是否可用,返回的狀態碼是 304(not modified)
與強快取相關的 http 頭有 cache-control
和 Expires
。比如Cache-control: max-age=N
的頭,相應的快取的壽命就是N,對於不含這個屬性的請求,則會去查看是否包含 Expires
屬性。
與協商快取相關的是 Etag
和 Last-Modified
。比如,用戶再次訪問給定的URL(設有ETag欄位),若資源過期了,客戶端就發送值為ETag的If-None-Match
header欄位。Last-Modified 由於精確度比 ETag 要低,所以這是一個備用機制
Tip: 對於單頁面,我們可以通過更新頁面(例如 index.html)中引用資源的路徑,讓瀏覽器重新載入資源,更多介紹請看 MDN HTTP 快取、靜態資源的快取。
https 為什麼安全
答:https 即超文本傳輸安全協議。https 經由 http 進行通訊,但利用 SSL/TLS
(安全協議) 來加密數據包。之所以說 https 安全,主要是因為它有一套比較好的數據傳輸方案。
比如 A 向 B 發送消息,A 會通過 B 提供的公鑰
對數據進行加密後再傳輸。並且 A 不是直接從 B 那裡獲得公鑰,而是從認證機構
取得。
也就能保證:
- 數據是以密文的形式進行傳輸,且只能被服務端配對的的
私鑰
進行解密 - A 取得的
公鑰
來自認證機構,保證了公鑰確實是 B 的,而非偽造的
Tip:相關術語介紹:
- 加密演算法和解密演算法統稱為
密碼演算法
密鑰
是使用密碼演算法中輸入的一段參數。同一個明文在相同的密碼演算法和不同的密鑰下會產生不同的密文。根據密鑰的使用方法,可分為對稱加密和公鑰加密對稱加密
,加密和解密使用相同的密鑰公鑰加密
,分為加密密鑰(或稱公鑰
)和解密密鑰(私鑰
),其中任何人都可以獲取公鑰,而私鑰只能自己使用- 傳輸層安全性協議(英語:Transport Layer Security,縮寫:
TLS
)及其前身安全套接層(英語:Secure Sockets Layer,縮寫:SSL
)是一種安全協議,目的是為互聯網通訊提供安全及數據完整性保障
注:http 的默認埠是 80,而 https 的默認埠是 443。
描述下瀏覽器輸入 url 回車後的整個過程
答:
大概流程:
- 首先拿到伺服器 ip(即域名解析)
- 接著和伺服器建立連接,傳遞數據(即相互通訊)
- 伺服器處理請求
- 瀏覽器接收響應
更具體一些:
域名解析
拿到伺服器 ip 需要用到 域名解析(將好記的域名解析成 ip)。
解析流程大致如下圖所示:
- 客戶端:我要訪問
www.163.com
,請告訴我 ip 本地 dns 伺服器
:快取里找不到,去 dns 根伺服器詢問dns 根伺服器
:這個域名由 .com 負責.com 域伺服器
:163.com 域名應該知道163.com 域伺服器
:ip 是1.1.1.xx
- 本地 dns 伺服器取得
www.163.com
對應的 ip,寫入快取,返回 ip 給客戶端
Tip:
- 真實的解析過程情況會更多,比如會搜索系統中 hosts 文件。筆者很久以前用過一個叫 SwitchHosts(能管理、切換多個 hosts) 的工具,開發中用於切換不同的環境,比如開發環境、測試環境等等。
- dns(即域名系統),用於管理有意義的域名和ip的對應關係
相互通訊
主要是涉及 TCP/IP協議(一個協議族,能夠在多個不同網路間實現資訊傳輸,也是 Internet 最基本的協議)。
大致流程如下:
- 瀏覽器發起 http 請求
- 經由應用層
- 然後到傳輸層,其中 tcp 會使用三次握手建立連接,後面則會使用四次握手中斷連接
- 在往下就是網路層,比如 ip 定址
- 最後是網路介面層,這裡有傳輸數據的物理媒介
- 服務端接收則會反過來,依次是網路介面層,網路層,傳輸層,應用層
Tip:不同種類的應用程式會根據自己的需要來使用應用層的不同協議,比如瀏覽器在這裡會使用 http 協議。
瀏覽器接收響應
大致流程如下:
- 瀏覽器接收到資源後,會根據響應內容做不同的處理
- 比如響應碼是 301 則永久重定向,如果是 304 則去讀快取
- 比如資源使用 gzip 壓縮了,就得解壓
- 比如資源類型是影片(
Content-Type:video/mp4
),瀏覽器則會播放 - 比如是否對資源做快取
Tip:有關構建 dom 樹、預載入掃描器、構建 cssom樹、迴流、重繪等可以看 這裡
零碎
- 如果輸入的不是合法的 url,而是關鍵字,那麼瀏覽器就會去搜索該關鍵字
- 如果之前已經訪問過,那麼會先走強快取,如果過期就走協商快取
- 可能還會涉及 sts,例如通過 http 而非 https 來訪問淘寶、百度、京東的首頁,則會發生一個 307 的重定向,並轉到 https 上來
webpack
簡單說一下 tree-shaking (樹搖)
答:用於描述移除 JavaScript 上下文中的未引用程式碼
Tip: 更多細節請看 tree-shaking
說一下你知道的 loader
答:由於 webpack 只能識別 javascript,其他資源都需要相應的 loader 來做翻譯。
與css相關的有:css-loader、style-loader、less-loader、sass-loader、postcss-loader
配合圖片的有:url-loader、file-loader
與 svg 相關的有:svgo-loader
與 js 有關的有:babel-loader、ts-loader
與 vue 相關的有:vue-loader、vue-style-loader
Tip:以下是相應 loader 的簡易介紹:
css-loader
,將 css 文件翻譯成 webpack 能識別style-loader
,將 css 注入 dom。less-loader
,用於將 less 轉為 css。類似的有 sass-loader、stylus-loaderpostcss-loader
,webpack 它用來使用 postcssurl-loader
,將圖片轉為 base64file-loader
,配合 url-loader 可以生成圖片label-loader
,用於在 webpack 中使用 babelts-loader
,與 typescript 相關svgo-loader
,與 svg 相關,用於 svg 優化vue-loader
,它允許你以一種名為單文件組件 (SFCs)的格式撰寫 Vue 組件vue-style-loader
,是一個基於 style-loader 的 fork,作為依賴項包含在 vue-loader 中,無需下載 1. vue-style-loader 即可使用。
請說一說 loader 和 plugin
答:
webpack 沒有特殊配置只能識別 javascript,而前端還有css、圖片等其他資源。
loader 用於對模組的源程式碼進行轉換,可以理解成」翻譯官「,比如我們需要使用 css,那麼可以用 css-loader,如果需要使用圖片,我們可以使用 url-loader 和 file-loader,如果需要將箭頭函數翻譯成普通函數,可以使用 babel-loader。loader 本質上是導出為函數的 JavaScript 模組。
而插件的目的是用於解決 loader 無法是現實其他事,plugin 強調一個事件監聽能力,能在 webpack 內部監聽一些事件,並且能改變一些文件打包後輸出的結果。比如我們可以使用 html-webpack-plugin 來幫我們創建 html 並自動引用打包後的資源。plugin 本質上是一個具有 apply 方法的類。
Tip:有關 loader 和 plugin 的本質介紹可以查看 這裡。
請說一下 vue-loader
答:vue-loader 是一個 webpack 的 loader,它允許我們能以單文件組件的格式編寫 vue 組件。同時它也提供了一些其他特性,比如:提供熱重載、提供 scoped css、允許為 vue 組件的每個部分使用其他的 webapck loader(如 style 部分使用 sass,template 部分使用 pug)。總之,webpack 配合 vue-loader 能幫助我們編寫 vue 應用。
Tip: 更多介紹請看 vue-loader
其他
能否說說 lodash.js 的深拷貝
答:沒看過 lodash.js,但我用過 jQuery 的 extend 方法,如果要深拷貝,則傳 true 給第一個參數
深度作用選擇器
修改 element-ui 樣式,比如將表格組件的表頭改為紅色,通常是不起作用,就像下面這樣:
<template>
<el-table></el-table>
</template>
<style scoped>
.el-table thead {
color: red;
}
</style>
我們可以怎麼做?
答:可以使用 深度作用選擇器。即三個大於號(>>>),就像這樣:
<style scoped>
>>> .el-table thead {
color: red;
}
</style>
子域名跨域 Document.domain
不修改後端伺服器,只在前端設置,如何讓 a.baidu.com 和 b.baidu.com 能相互訪問
答:可以使用 Document.domain
平時你是如何封裝 axios
答:會簡單封裝下 axios。比如創建一個 request.js 的文件,裡面返回一個 axios 實例,再做一下請求攔截和響應攔截。請求攔截器可以統一給所有請求傳遞一些資訊給伺服器,比如 token,在響應攔截中,比如返回 code 不是2000,則拋出錯誤,比如是 3000 就說明需要重新登錄等等。
如何將二進位數據顯示成圖片
答:或許可以利用 URL、Blob。就像這樣:
var blob = new Blob(...);
var url = URL.createObjectURL(blob)
imageElement.setAttribute('src', url)
一般通過什麼途徑來學習
答:比較雜,比如:騰訊 Web 前端、百度前端、淘寶前端、凹凸實驗室、奇舞團,或者 github(看開源項目)、npm、babel、掘金、簡書、部落格園、紙質書、線上影片(如 慕課網、bilibili、極客學院)、技術相關的官網(如 node、express、webpack)、mdn、維基百科等等
簡單談一下跨域
答:跨域方式有很多,但現在主流的有兩種,一種是開發環境和生成環境都使用 cors(跨域資源共享),一種是開發環境用 proxy,而生產環境用 nginx。
推薦的是 cors,它能決定瀏覽器是否阻止前端 JavaScript 程式碼獲取跨域請求的響應。前端無需變化,工作量在後端。而後端的開發環境和生成環境是一套程式碼,所以也很方便使用。如果後端不願意,前端就用 proxy + nginx。
Tip:更多細節可以查看 這裡
請說一下 bfc(塊級格式化上下文)
答:bfc 有個特性,如果一個元素具有 bfc,那麼內部子元素在怎麼折騰,都不會影響外部元素。
所以 bfc 元素不會有 margin 重疊。bfc 元素也能用來清除浮動的影響,子元素浮動會導致父元素高度塌陷,則會影響後面布局,這有悖於前面的特性。
創建 bfc 有多種方式,比如 overflow:auto/hidden
、display:table-cell
是否用過 chrome 中 Performance
答:用過。通常在無痕模式下進行來分析性能,比如可以通過勾選記憶體
其來分析記憶體使用情況,或使用調用樹
來找出花費時間較長的函數等。
是否了解過微服務、微前端
答:是的。微前端第一提出好似在 2016年底,將微服務這個被應用於服務端的技術擴展到前端領域。微前端是一種多個團隊通過獨立發布功能的方式來共同構建現代化 web 應用的技術手段及方法策略。比如 螞蟻金融開源一套完整的微前端解決方案,即 qiankun(對於用戶而言只是一個類似 jQuery 的庫)。
是否自己寫過 npm 包
答:寫過。
Tip:如何發布一個 npm 包請看 這裡
結束
在羅列一點剩餘的題目:
- 介紹一下 vue,就當我是 vue 小白
- 是否了解 csrf,能否簡單談一下
- 如果做單頁面應用的首頁優化
- 是否了解 vue 中的 $listeners、$attrs
- 請介紹一下 http1 和 http2 的關係和區別
- 比如讓你制定項目規範,你會從什麼方面著手
- 請說說你知道的 http 頭部(headers)
- 請說說你知道的 http 狀態碼
- 是否使用過 corss-env
把心放平,輕裝上陣,祝君好運。