一年半經驗,百度、有贊、阿里前端面試總結

麥樂丶發表於2018-11-17

前言

人家都說,前端需要每年定期出來面面試,衡量一下自己當前的技術水平以及價值,本人17年7月份,畢業到現在都沒出來試過,也沒很想換工作,就出來試試,看看自己水平咋樣。

以下為我現場面試時候的一些回答,部分因人而異的問題我就不回答了,回答的都為參考答案,也有部分錯誤的地方或者不好的地方,有更好的答案的可以在評論區評論。

原文地址

百度 WEB前端工程師 連續五面 全程3約個小時

一面

先完成筆試題

  1. 實現一個函式,判斷輸入是不是迴文字串。
function run(input) {
  if (typeof input !== `string`) return false;
  return input.split(``).reverse().join(``) === input;
}
複製程式碼
  1. 兩種以上方式實現已知或者未知寬度的垂直水平居中。

// 1
.wrapper {
  position: relative;
  .box {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 100px;
    height: 100px;
    margin: -50px 0 0 -50px;
  }
}

// 2
.wrapper {
  position: relative;
  .box {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
  }
}

// 3
.wrapper {
  .box {
    display: flex;
    justify-content:center;
    align-items: center;
    height: 100px;
  }
}

// 4
.wrapper {
  display: table;
  .box {
    display: table-cell;
    vertical-align: middle;
  }
}

複製程式碼
  1. 實現效果,點選容器內的圖示,圖示邊框變成border 1px solid red,點選空白處重置。
const box = document.getElementById(`box`);
function isIcon(target) {
  return target.className.includes(`icon`);
}

box.onclick = function(e) {
  e.stopPropagation();
  const target = e.target;
  if (isIcon(target)) {
    target.style.border = `1px solid red`;
  }
}
const doc = document;
doc.onclick = function(e) {
  const children = box.children;
  for(let i; i < children.length; i++) {
    if (isIcon(children[i])) {
      children[i].style.border = `none`;
    }
  }
}
複製程式碼
  1. 請簡單實現雙向資料繫結mvvm。
<input id="input"/>
複製程式碼
const data = {};
const input = document.getElementById(`input`);
Object.defineProperty(data, `text`, {
  set(value) {
    input.value = value;
    this.value = value;
  }
});
input.onchange = function(e) {
  data.text = e.target.value;
}
複製程式碼
  1. 實現Storage,使得該物件為單例,並對localStorage進行封裝設定值setItem(key,value)和getItem(key)
var instance = null;
class Storage {
  static getInstance() {
    if (!instance) {
      instance = new Storage();
    }
    return instance;
  }
  setItem = (key, value) => localStorage.setItem(key, value),
  getItem = key => localStorage.getItem(key)
}
複製程式碼

Q1 你的技術棧主要是react,那你說說你用react有什麼坑點?

1、JSX做表示式判斷時候,需要強轉為boolean型別,如:

render() {
  const b = 0;
  return <div>
    {
      !!b && <div>這是一段文字</div>
    }
  </div>
}
複製程式碼

如果不使用 !!b 進行強轉資料型別,會在頁面裡面輸出 0。

2、儘量不要在 componentWillReviceProps 裡使用 setState,如果一定要使用,那麼需要判斷結束條件,不然會出現無限重渲染,導致頁面崩潰。(實際不是componentWillReviceProps會無限重渲染,而是componentDidUpdate)

3、給元件新增ref時候,儘量不要使用匿名函式,因為當元件更新的時候,匿名函式會被當做新的prop處理,讓ref屬性接受到新函式的時候,react內部會先清空ref,也就是會以null為回撥引數先執行一次ref這個props,然後在以該元件的例項執行一次ref,所以用匿名函式做ref的時候,有的時候去ref賦值後的屬性會取到null。
詳情見

4、遍歷子節點的時候,不要用 index 作為元件的 key 進行傳入。

Q2 我現在有一個button,要用react在上面繫結點選事件,要怎麼做?

class Demo {
  render() {
    return <button onClick={(e) => {
      alert(`我點選了按鈕`)
    }}>
      按鈕
    </button>
  }
}
複製程式碼

Q3 接上一個問題,你覺得你這樣設定點選事件會有什麼問題嗎?

由於onClick使用的是匿名函式,所有每次重渲染的時候,會把該onClick當做一個新的prop來處理,會將內部快取的onClick事件進行重新賦值,所以相對直接使用函式來說,可能有一點的效能下降(個人認為)。

修改

class Demo {

  onClick = (e) => {
    alert(`我點選了按鈕`)
  }

  render() {
    return <button onClick={this.onClick}>
      按鈕
    </button>
  }
}
複製程式碼

當然你在內部宣告的不是箭頭函式,然後你可能需要在設定onClick的時候使用bind繫結上下文,這樣的效果和先前的使用匿名函式差不多,因為bind會返回新的函式,也會被react認為是一個新的prop。

Q4 你說說event loop吧

首先,js是單執行緒的,主要的任務是處理使用者的互動,而使用者的互動無非就是響應DOM的增刪改,使用事件佇列的形式,一次事件迴圈只處理一個事件響應,使得指令碼執行相對連續,所以有了事件佇列,用來儲存待執行的事件,那麼事件佇列的事件從哪裡被push進來的呢。那就是另外一個執行緒叫事件觸發執行緒做的事情了,他的作用主要是在定時觸發器執行緒、非同步HTTP請求執行緒滿足特定條件下的回撥函式push到事件佇列中,等待js引擎空閒的時候去執行,當然js引擎執行過程中有優先順序之分,首先js引擎在一次事件迴圈中,會先執行js執行緒的主任務,然後會去查詢是否有微任務microtask(promise),如果有那就優先執行微任務,如果沒有,在去查詢巨集任務macrotask(setTimeout、setInterval)進行執行。

Q5 說說事件流吧

事件流分為兩種,捕獲事件流和冒泡事件流。

捕獲事件流從根節點開始執行,一直往子節點查詢執行,直到查詢執行到目標節點。

冒泡事件流從目標節點開始執行,一直往父節點冒泡查詢執行,直到查到到根節點。

DOM事件流分為三個階段,一個是捕獲節點,一個是處於目標節點階段,一個是冒泡階段。

Q6 我現在有一個進度條,進度條中間有一串文字,當我的進度條覆蓋了文字之後,文字要與進度條反色,怎麼實現?

。。。當時我給的是js的方案,在進度條寬度變化的時候,計算蓋過每一個文字的50%,如果超過,設定文字相反顏色。

當然css也有對應的方案,也就是 mix-blend-mode,我並沒有接觸過。

對應html也有對應方案,也就設定兩個相同位置但是顏色相反的dom結構在重疊在一起,頂層覆蓋底層,最頂層的進度條取overflow為hidden,其寬度就為進度。

二面

Q1 你為什麼要離開上一家公司?

Q2 你覺得理想的前端地位是什麼?

Q3 那你意識到問題所在,你又嘗試過解決問題嗎?

三面

Q1 說一下你上一家公司的一個整體開發流程吧

Q2 react 的虛擬dom是怎麼實現的

首先說說為什麼要使用Virturl DOM,因為操作真實DOM的耗費的效能代價太高,所以react內部使用js實現了一套dom結構,在每次操作在和真實dom之前,使用實現好的diff演算法,對虛擬dom進行比較,遞迴找出有變化的dom節點,然後對其進行更新操作。為了實現虛擬DOM,我們需要把每一種節點型別抽象成物件,每一種節點型別有自己的屬性,也就是prop,每次進行diff的時候,react會先比較該節點型別,假如節點型別不一樣,那麼react會直接刪除該節點,然後直接建立新的節點插入到其中,假如節點型別一樣,那麼會比較prop是否有更新,假如有prop不一樣,那麼react會判定該節點有更新,那麼重渲染該節點,然後在對其子節點進行比較,一層一層往下,直到沒有子節點。

Q3 react 的渲染過程中,兄弟節點之間是怎麼處理的?也就是key值不一樣的時候。

通常我們輸出節點的時候都是map一個陣列然後返回一個ReactNode,為了方便react內部進行優化,我們必須給每一個reactNode新增key,這個key prop在設計值處不是給開發者用的,而是給react用的,大概的作用就是給每一個reactNode新增一個身份標識,方便react進行識別,在重渲染過程中,如果key一樣,若元件屬性有所變化,則react只更新元件對應的屬性;沒有變化則不更新,如果key不一樣,則react先銷燬該元件,然後重新建立該元件。

Q4 我現在有一個陣列[1,2,3,4],請實現演算法,得到這個陣列的全排列的陣列,如[2,1,3,4],[2,1,4,3]。。。。你這個演算法的時間複雜度是多少

這個我沒寫出來,大概給了個思路,將每一個陣列拆除倆個小陣列進行求它的全排列,然後得到的結果互相之間又進行全排列,然後把最後的結果連線起來。。。

感興趣的同學見陣列全排列

Q5 我現在有一個揹包,容量為m,然後有n個貨物,重量分別為w1,w2,w3…wn,每個貨物的價值是v1,v2,v3…vn,w和v沒有任何關係,請求揹包能裝下的最大價值。

這個我也沒寫出來,也給了個思路,首先使用Q4的方法得到貨物重量陣列的全組合(包括拆分成小陣列的全組合),然後計算每一個組合的價值,並進行排序,然後遍歷陣列,找到價值較高切剛好能裝進揹包m的組合。

本題動態規劃面試題,感興趣的同學請自行百度或者谷歌。

四面

Q1 請說一下你的上一家公司的研發釋出流程。

Q2 你說一下webpack的一些plugin,怎麼使用webpack對專案進行優化。

正好最近在做webpack構建優化和效能優化的事兒,當時吹了大概15~20分鐘吧,外掛請見webpack外掛歸納總結

構建優化

1、減少編譯體積 ContextReplacementPugin、IgnorePlugin、babel-plugin-import、babel-plugin-transform-runtime。

2、並行編譯 happypack、thread-loader、uglifyjsWebpackPlugin開啟並行

3、快取 cache-loader、hard-source-webpack-plugin、uglifyjsWebpackPlugin開啟快取、babel-loader開啟快取

4、預編譯 dllWebpackPlugin && DllReferencePlugin、auto-dll-webapck-plugin

效能優化

1、減少編譯體積 Tree-shaking、Scope Hositing。

2、hash快取 webpack-md5-plugin

3、拆包 splitChunksPlugin、import()、require.ensure

Q3 es6 class 的new例項和es5的new例項有什麼區別

這個我覺得是一樣的(當時因為很少看babel編譯之後的結果),面試官說不一樣。。。後來我看了一下babel的編譯結果,發現只是類的方法宣告的過程不一樣而已,最後new的結果是一樣的。。。具體答案現在我也不知道。。。

Q4 看你簡歷上寫了canvas,你說一下為什麼canvas的圖片為什麼過有跨域問題。

canvas圖片為什麼跨域我不知道,至今沒查出來,也差不多,大概跨域原因和瀏覽器跨域的原因是一樣的吧。

Q5 我現在有一個canvas,上面隨機布著一些黑塊,請實現方法,計算canvas上有多少個黑塊。

使用getImageData獲取畫素陣列,然後遍歷陣列,把在遍歷節點的過程中,檢視節點上下左右的畫素顏色是否相同,如果相同,然後設定標識,最後groupBy一下所有畫素。(這是我當時的方案)

其他更好的答案見地址

Q6 請手寫實現一個promise

這個就不寫了,詳情見promise實現原理

注:四面是一個超級可愛的小姐姐,電腦給我讓我寫完之後,我說我寫得差不多了,然後電腦給她,然後她竟然默默的在看我的程式碼,嘗試尋找我的思路,也沒有問我實現思路是啥,然後我就問她,你不應該是讓我給你解釋我的程式碼思路嗎。。。你竟然在嘗試尋找我的思路,我自己都不知道我自己是思路是啥。。。然後我兩都笑了,哈哈哈。最後結束的時候我說我午飯還沒吃,她還叫了另外一個小哥哥先帶了下去吃飯,真是一個善良的小姐姐,非常感謝。

五面

Q1 你說一下你的技術有什麼特點

Q2 說一下你覺得你最得意的一個專案?你這個專案有什麼缺陷,弊端嗎?

Q3 現在有那麼一個團隊,假如讓你來做技術架構,你會怎麼做?

考慮到團隊每一個前端的技術棧可能不一致,這個時候我可能選擇微前端架構,讓每個人負責的模組可以單獨開發,單獨部署,單獨回滾,不依賴於其他專案模組,在儘可能的情況下節約團隊成員之間的學習成本,當然這肯定也有缺點,那就是每個模組都需要一個前端專案,單獨部署,單獨回滾無疑也加大了運維成本。

Q4 說一下你上一家公司的主要業務流程,你參與到其中了嗎?

杭州有贊

一面 WEB前端工程師 電話面 全程43分鐘

Q1 自我介紹

Q2 說說從輸入URL到看到頁面發生的全過程,越詳細越好。

  1. 首先瀏覽器主程式接管,開了一個下載執行緒。
  2. 然後進行HTTP請求(DNS查詢、IP定址等等),中間會有三次捂手,等待響應,開始下載響應報文。
  3. 將下載完的內容轉交給Renderer程式管理。
  4. Renderer程式開始解析css rule tree和dom tree,這兩個過程是並行的,所以一般我會把link標籤放在頁面頂部。
  5. 解析繪製過程中,當瀏覽器遇到link標籤或者script、img等標籤,瀏覽器會去下載這些內容,遇到時候快取的使用快取,不適用快取的重新下載資源。
  6. css rule tree和dom tree生成完了之後,開始合成render tree,這個時候瀏覽器會進行layout,開始計算每一個節點的位置,然後進行繪製。
  7. 繪製結束後,關閉TCP連線,過程有四次揮手。

Q3 你剛剛說了三次握手,四次揮手,那你描述一下?

本人對計算機網路的這些概念一直不是很熟悉,所以這個問題回答不會,這裡mark下文章,感興趣的同學檢視地址

Q4 剛剛Q2中說的CSS和JS的位置會影響頁面效率,為什麼?

css在載入過程中不會影響到DOM樹的生成,但是會影響到Render樹的生成,進而影響到layout,所以一般來說,style的link標籤需要儘量放在head裡面,因為在解析DOM樹的時候是自上而下的,而css樣式又是通過非同步載入的,這樣的話,解析DOM樹下的body節點和載入css樣式能儘可能的並行,加快Render樹的生成的速度。

js指令碼應該放在底部,原因在於js執行緒與GUI渲染執行緒是互斥的關係,如果js放在首部,當下載執行js的時候,會影響渲染行程繪製頁面,js的作用主要是處理互動,而互動必須得先讓頁面呈現才能進行,所以為了保證使用者體驗,儘量讓頁面先繪製出來。

Q5 現在有一個函式A和函式B,請你實現B繼承A

// 方式1
function B(){}
function A(){}
B.prototype = new A();

// 方式2
function A(){}
function B(){
  A.call(this);
}

// 方式3
function B(){}
function A(){}
B.prototype = new A();

function B(){
  A.call(this);
}
複製程式碼

Q6 剛剛你在Q5中說的幾種繼承的方式,分別說說他們的優缺點

方式1:簡單易懂,但是無法實現多繼承,父類新增原型方法/原型屬性,子類都能訪問到

方式2:可以實現多繼承,但是隻能繼承父類的例項屬性和方法,不能繼承原型屬性/方法

方式3:可以繼承例項屬性/方法,也可以繼承原型屬性/方法,但是示例了兩個A的建構函式

Q7 說說CSS中幾種垂直水平居中的方式

參考前面百度一面筆試題Q2

Q8 Q7中說的flex佈局,垂直水平居中必須知道寬度嗎?

是的,必須知道高度(腦子進水了回答了必須知道,其實答案是不需要知道高度的)

Q9 描述一下this

this,函式執行的上下文,可以通過apply,call,bind改變this的指向。對於匿名函式或者直接呼叫的函式來說,this指向全域性上下文(瀏覽器為window,nodejs為global),剩下的函式呼叫,那就是誰呼叫它,this就指向誰。當然還有es6的箭頭函式,箭頭函式的指向取決於該箭頭函式宣告的位置,在哪裡宣告,this就指向哪裡。

Q10 說一下瀏覽器的快取機制

瀏覽器快取機制有兩種,一種為強快取,一種為協商快取。

對於強快取,瀏覽器在第一次請求的時候,會直接下載資源,然後快取在本地,第二次請求的時候,直接使用快取。

對於協商快取,第一次請求快取且儲存快取標識與時間,重複請求向伺服器傳送快取標識和最後快取時間,服務端進行校驗,如果失效則使用快取。

強快取方案

Exprires:服務端的響應頭,第一次請求的時候,告訴客戶端,該資源什麼時候會過期。Exprires的缺陷是必須保證服務端時間和客戶端時間嚴格同步。

Cache-control:max-age,表示該資源多少時間後過期,解決了客戶端和服務端時間必須同步的問題,

協商快取方案

If-None-Match/ETag:快取標識,對比快取時使用它來標識一個快取,第一次請求的時候,服務端會返回該標識給客戶端,客戶端在第二次請求的時候會帶上該標識與服務端進行對比並返回If-None-Match標識是否表示匹配。

Last-modified/If-Modified-Since:第一次請求的時候服務端返回Last-modified表明請求的資源上次的修改時間,第二次請求的時候客戶端帶上請求頭If-Modified-Since,表示資源上次的修改時間,服務端拿到這兩個欄位進行對比。

Q11 ETag是這個字串是怎麼生成的?

沒答出來,我當時猜是根據檔案內容或者最後修改時間進行的加密演算法。其實官方沒有明確指定生成ETag值的方法。

通常,使用內容的雜湊,最後修改時間戳的雜湊值,或簡單地使用版本號。

Q12 現在要你完成一個Dialog元件,說說你設計的思路?它應該有什麼功能?

  1. 該元件需要提供hook指定渲染位置,預設渲染在body下面。
  2. 然後改元件可以指定外層樣式,如寬度等
  3. 元件外層還需要一層mask來遮住底層內容,點選mask可以執行傳進來的onCancel函式關閉Dialog。
  4. 另外元件是可控的,需要外層傳入visible表示是否可見。
  5. 然後Dialog可能需要自定義頭head和底部footer,預設有頭部和底部,底部有一個確認按鈕和取消按鈕,確認按鈕會執行外部傳進來的onOk事件,然後取消按鈕會執行外部傳進來的onCancel事件。
  6. 當元件的visible為true時候,設定body的overflow為hidden,隱藏body的滾動條,反之顯示滾動條。
  7. 元件高度可能大於頁面高度,元件內部需要滾動條。
  8. 只有元件的visible有變化且為ture時候,才重渲染元件內的所有內容。

Q13 你覺得你做過的你覺得最值得炫耀的專案?

螞蟻金服-體驗技術部 資深資料視覺化研發工程師

一面 電話面 全程1小時24分鐘

Q1 描述一下你最近做的視覺化的專案

Q2 剛剛說的java呼叫js離線生成資料包告?java呼叫js的promise非同步返回結果怎麼實現的?

使用java的js引擎Nashorn,Nashorn不支援事件佇列,是要引進polyfill,然後java呼叫js方法獲得java的promise物件,然後在呼叫該物件的then方法,回撥函式為java中的某各類的某個方法,然後while一個表示是否已執行回撥的變數,如果未執行,則讓java主執行緒sleep,如果已經執行,則跳出迴圈,表示是否已執行回撥的變數在傳入promise的回撥函式中設定更改。詳情程式碼見地址

Q3 說說svg和canvas各自的優缺點?

共同點:都是有效的圖形工具,對於資料較小的情況下,都很又高的效能,它們都使用 JavaScript 和 HTML;它們都遵守全球資訊網聯合會 (W3C) 標準。

svg優點:

向量圖,不依賴於畫素,無限放大後不會失真。

以dom的形式表示,事件繫結由瀏覽器直接分發到節點上。

svg缺點:

dom形式,涉及到動畫時候需要更新dom,效能較低。

canvas優點:

定製型更強,可以繪製繪製自己想要的東西。

非dom結構形式,用JavaScript進行繪製,涉及到動畫效能較高。

canvas缺點:

事件分發由canvas處理,繪製的內容的事件需要自己做處理。

依賴於畫素,無法高效保真,畫布較大時候效能較低。

Q4 你剛剛說的canvas渲染較大畫布的時候效能會較低?為什麼?

因為canvas依賴於畫素,在繪製過程中是一個一個畫素去繪製的,當畫布足夠大,畫素點也就會足夠多,那麼想能就會足夠低。

Q6 假設我現在有5000個圓,完全繪製出來,點選某一個圓,該圓高亮,另外4999個圓設為半透明,分別說說用svg和canvas怎麼實現?

首先,從資料出發,我們的每個圓是一個資料,這個資料有圓的x、y、radius、isHighlight如果是svg,直接渲染節點即可,然後往節點上邊繫結點選事件,點選改變所有資料的高亮屬性(必須同步執行完成),然後讓瀏覽器進行繪製。如果是canvas,我們需要自己繫結事件到canvans標籤上,然後點選的時候判斷點選的位置是否在圓內,如果在某個圓內,則更新所有資料的高亮屬性,之後在進行一次性繪製。

Q7 剛剛說的canvas的點選事件,怎麼樣實現?假如不是圓,這些圖形是正方形、長方形、規則圖形、不規則圖形呢。

針對於每一個形狀,將其抽象成shape類,每一個類有自己的方法isPointInSide來判斷節點是否在圖形內,對於不規則圖形,當做矩形處理,點選的時候執行該方法判斷點選位置是否在圖形內。

Q8 那假如我的圖形可能有變形、放大、偏移、旋轉的需求呢?你的這個isPointInSide怎麼處理?

這個我答不出來,據面試官提示,好像有相應的API處理變形、旋轉、放大等等之後的位置對映關係。

Q9 那個這個canvas的點選事件,點選的時候怎麼樣快速的從這5000個圓中找到你點選的那個圓(不完全遍歷5000個節點)?

可以通過預查詢的形式,當滑鼠劃過的時候預先查詢到滑鼠附近的一些節點,當點選的時候在從這些預先篩選好的節點裡查詢點選下來的節點,當然這個方法的前提是不能影響js主執行緒的執行,必須是非同步的形式。

Q10 那你用過@antv/g6,裡面有一個tree,說說你大學時候接觸到的tree的資料結構是怎麼實現的?

畢業一年多,tree的結構大概忘記了,我當時是這麼回答的:

大學使用的是C++學的資料結構,是用指標的形式,首先有一個根節點,根節點裡有一個指標陣列指向它的所有子節點,然後每一個子節點也是,擁有著子節點的指標陣列,一層一層往下,直到為葉子節點,指標陣列指向為空。

Q11 還記得二叉樹嗎?描述二叉樹的幾種遍歷方式?

先序遍歷:若二叉樹非空,訪問根結點,遍歷左子樹,遍歷右子樹。

中序遍歷:若二叉樹非空,遍歷左子樹;訪問根結點;遍歷右子樹。

後序遍歷:若二叉樹非空,遍歷左子樹;遍歷右子樹;訪問根結點。

所有遍歷是以遞迴的形似,直到沒有子節點。

Q12 說說你記得的所有的排序,他們的原理是什麼?

氣泡排序:雙層遍歷,對比前後兩個節點,如果滿足條件,位置互換,直到遍歷結束。

快速排序:去陣列中間的那一個數,然後遍歷所有數,小於該數的push到一個陣列,大於該數的push到另外一個陣列,然後遞迴去排序這兩個陣列,最後將所有結果連線起來。

選擇排序:宣告一個陣列,每次去輸入陣列裡面找陣列中的最大值或者最小值,取出來後push到宣告的陣列中,直到輸入陣列為空。

Q13 說一下你覺得你做過的最複雜的專案?中間遇到的困難,以及你是怎麼解決的?


面試官:我這邊問題差不多問完了,你還有什麼問題?

我:很驚訝今天全都是問視覺化相關的,沒怎麼問js,css,html。

面試官:那我們繼續吧

我:。。。


Q14 那給我介紹一下react吧(面試官是做視覺化開發的,根本不懂react)

以前我們沒有jquery的時候,我們大概的流程是從後端通過ajax獲取到資料然後使用jquery生成dom結果然後更新到頁面當中,但是隨著業務發展,我們的專案可能會越來越複雜,我們每次請求到資料,或則資料有更改的時候,我們又需要重新組裝一次dom結構,然後更新頁面,這樣我們手動同步dom和資料的成本就越來越高,而且頻繁的操作dom,也使我我們頁面的效能慢慢的降低。

這個時候mvvm出現了,mvvm的雙向資料繫結可以讓我們在資料修改的同時同步dom的更新,dom的更新也可以直接同步我們資料的更改,這個特定可以大大降低我們手動去維護dom更新的成本,mvvm為react的特性之一,雖然react屬於單項資料流,需要我們手動實現雙向資料繫結。

有了mvvm還不夠,因為如果每次有資料做了更改,然後我們都全量更新dom結構的話,也沒辦法解決我們頻繁操作dom結構(降低了頁面效能)的問題,為了解決這個問題,react內部實現了一套虛擬dom結構,也就是用js實現的一套dom結構,他的作用是講真實dom在js中做一套快取,每次有資料更改的時候,react內部先使用演算法,也就是鼎鼎有名的diff演算法對dom結構進行對比,找到那些我們需要新增、更新、刪除的dom節點,然後一次性對真實DOM進行更新,這樣就大大降低了操作dom的次數。

那麼diff演算法是怎麼運作的呢,首先,diff針對型別不同的節點,會直接判定原來節點需要解除安裝並且用新的節點來裝載解除安裝的節點的位置;針對於節點型別相同的節點,會對比這個節點的所有屬性,如果節點的所有屬性相同,那麼判定這個節點不需要更新,如果節點屬性不相同,那麼會判定這個節點需要更新,react會更新並重渲染這個節點。

react設計之初是主要負責UI層的渲染,雖然每個元件有自己的state,state表示元件的狀態,當狀態需要變化的時候,需要使用setState更新我們的元件,但是,我們想通過一個元件重渲染它的兄弟元件,我們就需要將元件的狀態提升到父元件當中,讓父元件的狀態來控制這兩個元件的重渲染,當我們元件的層次越來越深的時候,狀態需要一直往下傳,無疑加大了我們程式碼的複雜度,我們需要一個狀態管理中心,來幫我們管理我們狀態state。

這個時候,redux出現了,我們可以將所有的state交給redux去管理,當我們的某一個state有變化的時候,依賴到這個state的元件就會進行一次重渲染,這樣就解決了我們的我們需要一直把state往下傳的問題。redux有action、reducer的概念,action為唯一修改state的來源,reducer為唯一確定state如何變化的入口,這使得redux的資料流非常規範,同時也暴露出了redux程式碼的複雜,本來那麼簡單的功能,卻需要完成那麼多的程式碼。

後來,社群就出現了另外一套解決方案,也就是mobx,它推崇程式碼簡約易懂,只需要定義一個可觀測的物件,然後哪個組價使用到這個可觀測的物件,並且這個物件的資料有更改,那麼這個元件就會重渲染,而且mobx內部也做好了是否重渲染元件的生命週期shouldUpdateComponent,不建議開發者進行更改,這使得我們使用mobx開發專案的時候可以簡單快速的完成很多功能,連redux的作者也推薦使用mobx進行專案開發。但是,隨著專案的不斷變大,mobx也不斷暴露出了它的缺點,就是資料流太隨意,出了bug之後不好追溯資料的流向,這個缺點正好體現出了redux的優點所在,所以針對於小專案來說,社群推薦使用mobx,對大專案推薦使用redux。

Q15 假如我一個元件有一個狀態count為1,然後我在componentDidMount()裡面執行執行了兩次this.setState({count: ++this.state.count}),然後又執行了兩次setTimeout(() => { this.setState({count: ++this.state.count}) }, 0),最後count為多少?為什麼?

count為4,因為第二次執行setState的時候,取不到第一次this.state.count++的結果,react在一輪生命週期結束後才會更新內部的state,如果在一輪生命週期內多次使用了setState,react內部會有一個欄位isBatchUpdate標識本次更新為批量更新,然後在最後render的時候將所有setState的結果提交到state中,一次性進行更新,並且把isBatchUpdate這個欄位設定為false。

針對於兩次setTimeout,js引擎會把這兩個setState丟到事件佇列中,等待js空閒了去執行,而我們的渲染函式render是同步執行的(react16版本預設沒有開啟非同步渲染),所以等我們render執行完全,也就是我們的state被同步完後,在取事件佇列裡面的setState進行執行,setTimeout的第二個setState也是一樣的,所以最後結果是4。

備註:這個count的答案似乎有疑問,寫了個demo,答案並不是4,Demo地址 jsfiddle.net/yacan8/5gsp…

Q16 說一下你覺得你做過的最值得你說的吧

最後

這幾輪面試的面試官都非常和藹好交流,百度的五輪面試不知道過了沒有,只記得五面的面試官說,你稍等一下,我去問一下其他人對你還有什麼其他要求,然後過了一會兒HR就喊我先回去了,叫我等HR面的訊息,如果沒通過,也不會在聯絡我了,已經過了四天了,但願後面有訊息吧。然後有贊、螞蟻金服的兩個一面都過了,因為每次面完試面試官問我還有什麼問題嗎?我都會詢問一下本次面試面試官對我的評論是啥。

後續傳送門 -> 記一次前端面試的全過程

相關文章