記錄面試中一些回答不夠好的題(Vue 居多) | 掘金技術徵文

三毛丶發表於2018-03-04

相關問題

  • 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 實現如下即可:

記錄面試中一些回答不夠好的題(Vue 居多) | 掘金技術徵文

實現方式如下:

<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 的實現。 有一張實現圖:

記錄面試中一些回答不夠好的題(Vue 居多) | 掘金技術徵文

其基本實現原理:

  • 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 是關鍵,這個過程可以概括如下:

記錄面試中一些回答不夠好的題(Vue 居多) | 掘金技術徵文

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.iows ,可以很好的幫助我們入門。而客戶端也只需要參照 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

相關文章