相關問題
- flex 佈局 與 grid 佈局。
- 實現 Vue SSR 。
- 從 SPA 使用最小成本遷移到 SSR 。
- 實現方法: (未完成) 根據指定元素,在陣列裡面找出 ff 陣列(ff 陣列這個名字是我瞎說的)。比如陣列 [2, 3, 6, 7] ,指定元素 7,則 ff 陣列是 [2, 2, 3](2+2+3 = 7)和 [7]。若指定元素 6,則 ff 陣列為 [2, 2, 2], [3, 3], 和 [6] 。
- 實現 Promise.finally。
- 另一種方式實現 Vue 的響應式原理。
- Vue 元件 data 為什麼必須是函式。
- Vue computed 實現。
- diff 演算法實現。
- Vue complier 實現。
- 快排及其優化。
- 快取演算法實現及其優化(快取演算法簡單模型:假設可以快取三個資料,請求前三個資料時,直接進快取列表,當請求第四個資料時,若命中快取,將被快取的資料放入快取列表頭部,否則把新加入的資料放入快取列表頭部,淘汰最後一個資料)。
- 怎麼快速定位哪個元件出現效能問題。
- http 狀態碼 202, 204 。
- WebSocket 。
- 儘可能多的說出你對 Electron 的理解。
相關解答
flex 佈局 與 grid 佈局
這個問題比較簡單,用 flex 與 grid 實現如下即可:
實現方式如下:
<html>
<head>
<style>
/* flex */
.box {
display: flex;
flex-wrap: wrap;
width: 100%;
}
.box div {
width: calc(100% / 3 - 2px);
height: 100px;
border: 1px solid black;
}
/* grid */
.box {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
width: 100%;
}
.box div {
height: 100px;
border: 1px solid black;
}
</style>
<head>
<body>
<div class="box">
<div></div>
<div></div>
<div></div>
<div></div>
</div>
<body>
</html>
複製程式碼
grid 學習:https://www.jianshu.com/p/d183265a8dad
實現 Vue SSR
一些想法寫在下題。
從 SPA 使用最小成本遷移到 SSR
Vue SSR 的好處就不多說了,這有一篇相關文章 服務端渲染與客戶端渲染 。 簡單的總結下 Vue SSR 的實現。 有一張實現圖:
其基本實現原理:
- app.js 作為客戶端與服務端的公用入口,匯出 Vue 根例項,供客戶端 entry 與服務端 entry 使用。客戶端 entry 主要作用掛載到 DOM 上,服務端 entry 除了建立和返回例項,還進行路由匹配與資料預獲取。
- webpack 為客服端打包一個 Client Bundle ,為服務端打包一個 Server Bundle 。
- 伺服器接收請求時,會根據 url,載入相應元件,獲取和解析非同步資料,建立一個讀取 Server Bundle 的 BundleRenderer,然後生成 html 傳送給客戶端。
- 客戶端混合,客戶端收到從服務端傳來的 DOM 與自己的生成的 DOM 進行對比,把不相同的 DOM 啟用,使其可以能夠響應後續變化,這個過程稱為客戶端啟用 。為確保混合成功,客戶端與伺服器端需要共享同一套資料。在服務端,可以在渲染之前獲取資料,填充到 stroe 裡,這樣,在客戶端掛載到 DOM 之前,可以直接從 store 裡取資料。首屏的動態資料通過
window.__INITIAL_STATE__
傳送到客戶端。
Vue SSR 的實現,主要就是把 Vue 的元件輸出成一個完整 HTML, vue-server-renderer 就是幹這事的。
純客戶端輸出過程有一個 complier 過程(「下題」中有一個簡單描述),主要作用是將 template 轉化成 render 字串 。
Vue SSR 需要做的事多點(輸出完整 HTML),除了 complier -> vnode,還需如資料獲取填充至 HTML、客戶端混合(hydration)、快取等等。
相比於其他模板引擎(ejs, jade 等),最終要實現的目的是一樣的,效能上可能要差點。
參考:
- https://ssr.vuejs.org/zh/
- https://segmentfault.com/a/1190000006701796
ff 陣列
實現 Promise.finally
finally 方法用於指定不管 Promise 物件最後狀態如何,都會執行的操作,使用方法如下:
Promise
.then(result => { ··· })
.catch(error => { ··· })
.finally(() => { ··· })
複製程式碼
finally 特點:
- 不接收任何引數。
- finally 本質上是 then 方法的特例。
Promise.prototype.finally = function (callback) {
let P = this.constructor
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
)
}
複製程式碼
另一種方式實現 Vue 的響應式原理
Vue 的響應式原理是使用 Object.defineProperty 追蹤依賴,當屬性被訪問或改變時通知變化。
有兩個不足之處:
- 不能檢測到增加或刪除的屬性。
- 陣列方面的變動,如根據索引改變元素,以及直接改變陣列長度時的變化,不能被檢測到。
原因差不多,無非就是沒有被 getter/setter 。
第一個比較容易理解,為什麼陣列長度不能被 getter/setter ?
在知乎上找了一個答案:如果你知道陣列的長度,理論上是可以預先給所有的索引設定 getter/setter 的。但是一來很多場景下你不知道陣列的長度,二來,如果是很大的陣列,預先加 getter/setter 效能負擔較大。
現在有一個替代的方案 Proxy,但這東西相容性不好,遲早要上的。
Proxy,在目標物件之前架設一層攔截。具體,可以參考 http://es6.ruanyifeng.com/#docs/reference
Vue 元件 data 為什麼必須是函式
理解兩點:
- 每個元件都是 Vue 的例項。
- 元件共享 data 屬性,當 data 的值是同一個引用型別的值時,改變其中一個會影響其他。
Vue computed 實現
這個題目有兩家問了,感覺都不是答得很好。
從兩個問題出發:
- 建立與其他屬性(如:data、 Store)的聯絡;
- 屬性改變後,通知計算屬性重新計算。
實現時,主要如下
- 初始化 data, 使用 Object.defineProperty 把這些屬性全部轉為 getter/setter。
- 初始化 computed, 遍歷 computed 裡的每個屬性,每個 computed 屬性都是一個 watch 例項。每個屬性提供的函式作為屬性的 getter,使用 Object.defineProperty 轉化。
- Object.defineProperty getter 依賴收集。用於依賴發生變化時,觸發屬性重新計算。
- 若出現當前 computed 計算屬性巢狀其他 computed 計算屬性時,先進行其他的依賴收集。
參考:https://segmentfault.com/a/1190000010408657
diff 演算法實現
以前寫過兩篇文章討論這個演算法的實現,沒想到過的太久,忘記了。(文章地址:https://github.com/jkchao/blog/issues/3 ,https://github.com/jkchao/blog/issues/4) 。 也好,稱此機會總結下
diff 的實現主要通過兩個方法,patchVnode 與 updateChildren 。
patchVnode 有兩個引數,分別是老節點 oldVnode, 新節點 vnode 。主要分五種情況:
- if (oldVnode === vnode),他們的引用一致,可以認為沒有變化。
- if(oldVnode.text !== null && vnode.text !== null && oldVnode.text !== vnode.text),文字節點的比較,需要修改,則會呼叫Node.textContent = vnode.text。
- if( oldCh && ch && oldCh !== ch ), 兩個節點都有子節點,而且它們不一樣,這樣我們會呼叫 updateChildren 函式比較子節點,這是diff的核心,後邊會講到。
- if (ch),只有新的節點有子節點,呼叫createEle(vnode),vnode.el已經引用了老的dom節點,createEle函式會在老dom節點上新增子節點。
- if (oldCh),新節點沒有子節點,老節點有子節點,直接刪除老節點。
updateChildren 是關鍵,這個過程可以概括如下:
oldCh 和 newCh 各有兩個頭尾的變數 StartIdx 和 EndIdx ,它們的2個變數相互比較,一共有4種比較方式。如果 4 種比較都沒匹配,如果設定了key,就會用key進行比較,在比較的過程中,變數會往中間靠,一旦 StartIdx > EndIdx 表明 oldCh 和 newCh 至少有一個已經遍歷完了,就會結束比較。
Vue complier 實現
以前寫過一篇 「Vue 生面週期總結的文章 」的文章,裡面提到了 complier 的作用,沒有做深入瞭解。。。
模板解析這種事,本質是將資料轉化為一段 html ,最開始出現在後端,經過各種處理吐給前端。隨著各種 mv* 的興起,模板解析交由前端處理。 總的來說,Vue complier 是將 template 轉化成一個 render 字串。 可以簡單理解成以下步驟:
- parse 過程,將 template 利用正則轉化成 AST 抽象語法樹。
- optimize 過程,標記靜態節點,後 diff 過程跳過靜態節點,提升效能。
- generate 過程,生成 render 字串。
參考:
- https://segmentfault.com/a/1190000006990480
- https://github.com/answershuto/learnVue/blob/master/docs/%E8%81%8A%E8%81%8AVue%E7%9A%84template%E7%BC%96%E8%AF%91.MarkDown
快排及其優化
前端對演算法的要求還是比較低的,但也是必不可少的一部分。
找到一篇比較不錯的文章:https://www.cnblogs.com/zichi/p/4788953.html
快取演算法實現及其優化
最簡單的一種思路就是使用陣列儲存,然後讓我優化。 我。。。一臉懵逼。 有興趣的同學可以參考這個: http://www.cnblogs.com/dolphin0520/p/3749259.html 。
ps: 看來我得補補資料結構和演算法相關的知識了。
怎麼快速定位哪個元件出現效能問題
當面試官問這個問題,沒有 get 到面試官的點,扯了一堆亂七八糟沒用的 - -。 後來面試官說主要是用 timeline 工具。 大意是通過 timeline 來檢視每個函式的呼叫時常,定位出哪個函式的問題,從而能判斷哪個元件出了問題。
附上兩個使用 timeline 的文章:
- https://juejin.im/post/5a6e78abf265da3e3f4cf085
- https://developers.google.cn/web/tools/chrome-devtools/?hl=zh-cn
http 狀態碼 202, 204
面試官不知道為何扯到了 202, 204。。。好像是由自己帶進坑的。- -
202: 伺服器已接受請求,但尚未處理。 204: 伺服器成功處理了請求,沒有返回任何內容。
這些狀態碼感覺只要能記住常用的就 ok 了,當然還得了解 200 +, 300+, 400+, 500+ 代表什麼意思。
WebSocket
WebSocket 應該算是一個比較常問的面試點,如果問的不深的話,應該比較好回答。
由於 http 存在一個明顯的弊端(訊息只能有客戶端推送到伺服器端,而伺服器端不能主動推送到客戶端),導致如果伺服器如果有連續的變化,這時只能使用輪詢,而輪詢效率過低,並不適合。於是 WebSocket 被發明出來。
相比與 http 具有以下有點:
- 支援雙向通訊,實時性更強;
- 可以傳送文字,也可以二進位制檔案;
- 協議識別符號是 ws,加密後是 wss ;
- 較少的控制開銷。連線建立後,ws客戶端、服務端進行資料交換時,協議控制的資料包頭部較小。在不包含頭部的情況下,服務端到客戶端的包頭只有2~10位元組(取決於資料包長度),客戶端到服務端的的話,需要加上額外的4位元組的掩碼。而HTTP協議每次通訊都需要攜帶完整的頭部;
- 支援擴充套件。ws協議定義了擴充套件,使用者可以擴充套件協議,或者實現自定義的子協議。(比如支援自定義壓縮演算法等)
- 無跨域問題。
實現比較簡單,服務端庫如 socket.io
、ws
,可以很好的幫助我們入門。而客戶端也只需要參照 api 實現即可。
參考:
- http://www.ruanyifeng.com/blog/2017/05/websocket.html
- https://www.cnblogs.com/chyingp/p/websocket-deep-in.html
儘可能多的說出你對 Electron 的理解
以前寫過一篇簡單的關於 electron-vue 的文章,沒想到真有面試官問,而且問的挺深的。
最最重要的一點,electron 實際上是一個套了 Chrome 的 node 程式。
所以應該是從兩個方面說開來:
- Chrome (無各種相容性問題);
- Node (Node 能做的它也能做)。
Chrome 沒什麼好說的,是個前端都懂。
Node 方面可說的就多了。
有個面試官問我,在 electron 怎麼解決跨域問題?
在我自己的專案裡,確實遇到了這個問題,可惜選擇了一個不怎麼好的方法的方法,設定 nginx 。
為什麼不好,如果專案是公司的,還需要運維同學幫忙。- -
也聊到了使用 CORS 允許跨域,也覺得不好,因為需要後端介面處理。 一臉懵逼的我,直到面試官提醒使用 node 來代理以下,才恍然大悟。(原來還可以這種操作。。。。)
當然也可以連線資料庫,上家公司本來打算要做一個 electron 配合連線資料庫的桌面應用。(還沒開始做就離職了- -) 挺可惜的,當時資料庫都已經選擇好了,leveldb 或者 lowdb ,覺得應該不難。
附上兩個 electron 配合資料庫使用的連結:
- https://github.com/typicode/lowdb/issues/169
- https://github.com/Level/electron-demo
功力不足,難免有錯誤之處,還望多多指出。
掘金技術證文活動連結: https://juejin.im/post/5aaf2a95f265da239b413aa1