百度、有贊、阿里前端面試總結(jié)
來源:
奇酷教育 發(fā)表于:
以下為百度、有贊、阿里的前端面試總結(jié),請大家參考:
以下為百度、有贊、阿里的前端面試總結(jié),請大家參考:
百度
WEB前端工程師 連續(xù)五面 全程3約個小時
一面
先完成筆試題:
1、實現(xiàn)一個函數(shù),判斷輸入是不是回文字符串。
2、兩種以上方式實現(xiàn)已知或者未知寬度的垂直水平居中。
3、實現(xiàn)效果,點擊容器內(nèi)的圖標(biāo),圖標(biāo)邊框變成border 1px solid red,點擊空白處重置。
4、請簡單實現(xiàn)雙向數(shù)據(jù)綁定mvvm。
5、實現(xiàn)Storage,使得該對象為單例,并對localStorage進行封裝設(shè)置值setItem(key,value)和
你的技術(shù)棧主要是react,那你說說你用react有什么坑點?
1、JSX做表達式判斷時候,需要強轉(zhuǎn)為boolean類型,如:
這是一段文本
如果不使用 !!b 進行強轉(zhuǎn)數(shù)據(jù)類型,會在頁面里面輸出 0。
2、盡量不要在 componentWillReviceProps 里使用 setState,如果一定要使用,那么需要判斷結(jié)束條件,不然會出現(xiàn)無限重渲染,導(dǎo)致頁面崩潰。
3、給組件添加ref時候,盡量不要使用匿名函數(shù),因為當(dāng)組件更新的時候,匿名函數(shù)會被當(dāng)做新的prop處理,讓ref屬性接受到新函數(shù)的時候,react內(nèi)部會先清空ref,也就是會以null為回調(diào)參數(shù)先執(zhí)行一次ref這個props,然后在以該組件的實例執(zhí)行一次ref,所以用匿名函數(shù)做ref的時候,有的時候去ref賦值后的屬性會取到null。詳情見
4、遍歷子節(jié)點的時候,不要用 index 作為組件的 key 進行傳入。
我現(xiàn)在有一個button,要用react在上面綁定點擊事件,要怎么做?
我點擊了按鈕') }}> 按鈕
接上一個問題,你覺得你這樣設(shè)置點擊事件會有什么問題嗎?
由于onClick使用的是匿名函數(shù),所有每次重渲染的時候,會把該onClick當(dāng)做一個新的prop來處理,會將內(nèi)部緩存的onClick事件進行重新賦值,所以相對直接使用函數(shù)來說,可能有一點的性能下降(個人認為)。
修改:
我點擊了按鈕') } render() { return
當(dāng)然你在內(nèi)部聲明的不是箭頭函數(shù),然后你可能需要在設(shè)置onClick的時候使用bind綁定上下文,這樣的效果和先前的使用匿名函數(shù)差不多,因為bind會返回新的函數(shù),也會被react認為是一個新的prop。
你說說event loop吧
首先,js是單線程的,主要的任務(wù)是處理用戶的交互,而用戶的交互無非就是響應(yīng)DOM的增刪改,使用事件隊列的形式,一次事件循環(huán)只處理一個事件響應(yīng),使得腳本執(zhí)行相對連續(xù),所以有了事件隊列,用來儲存待執(zhí)行的事件,那么事件隊列的事件從哪里被push進來的呢。那就是另外一個線程叫事件觸發(fā)線程做的事情了,他的作用主要是在定時觸發(fā)器線程、異步HTTP請求線程滿足特定條件下的回調(diào)函數(shù)push到事件隊列中,等待js引擎空閑的時候去執(zhí)行,當(dāng)然js引擎執(zhí)行過程中有優(yōu)先級之分,首先js引擎在一次事件循環(huán)中,會先執(zhí)行js線程的主任務(wù),然后會去查找是否有微任務(wù)microtask(promise),如果有那就優(yōu)先執(zhí)行微任務(wù),如果沒有,在去查找宏任務(wù)macrotask(setTimeout、setInterval)進行執(zhí)行。
說說事件流吧
事件流分為兩種,捕獲事件流和冒泡事件流。
捕獲事件流從根節(jié)點開始執(zhí)行,一直往子節(jié)點查找執(zhí)行,直到查找執(zhí)行到目標(biāo)節(jié)點。
冒泡事件流從目標(biāo)節(jié)點開始執(zhí)行,一直往父節(jié)點冒泡查找執(zhí)行,直到查到到根節(jié)點。
事件流分為三個階段,一個是捕獲節(jié)點,一個是處于目標(biāo)節(jié)點階段,一個是冒泡階段。
我現(xiàn)在有一個進度條,進度條中間有一串文字,當(dāng)我的進度條覆蓋了文字之后,文字要與進度條反色,怎么實現(xiàn)?
。。。當(dāng)時我給的是js的方案,在進度條寬度變化的時候,計算蓋過每一個文字的50%,如果超過,設(shè)置文字相反顏色。
當(dāng)然css也有對應(yīng)的方案,也就是 mix-blend-mode,我并沒有接觸過。
對應(yīng)html也有對應(yīng)方案,也就設(shè)置兩個相同位置但是顏色相反的dom結(jié)構(gòu)在重疊在一起,頂層覆蓋底層,最頂層的進度條取overflow為hidden,其寬度就為進度。
二面
你為什么要離開上一家公司?
你覺得理想的前端地位是什么?
那你意識到問題所在,你又嘗試過解決問題嗎?
三面
說一下你上一家公司的一個整體開發(fā)流程吧
的虛擬dom是怎么實現(xiàn)的
首先說說為什么要使用Virturl DOM,因為操作真實DOM的耗費的性能代價太高,所以react內(nèi)部使用js實現(xiàn)了一套dom結(jié)構(gòu),在每次操作在和真實dom之前,使用實現(xiàn)好的diff算法,對虛擬dom進行比較,遞歸找出有變化的dom節(jié)點,然后對其進行更新操作。為了實現(xiàn)虛擬DOM,我們需要把每一種節(jié)點類型抽象成對象,每一種節(jié)點類型有自己的屬性,也就是prop,每次進行diff的時候,react會先比較該節(jié)點類型,假如節(jié)點類型不一樣,那么react會直接刪除該節(jié)點,然后直接創(chuàng)建新的節(jié)點插入到其中,假如節(jié)點類型一樣,那么會比較prop是否有更新,假如有prop不一樣,那么react會判定該節(jié)點有更新,那么重渲染該節(jié)點,然后在對其子節(jié)點進行比較,一層一層往下,直到?jīng)]有子節(jié)點。
的渲染過程中,兄弟節(jié)點之間是怎么處理的?也就是key值不一樣的時候。
通常我們輸出節(jié)點的時候都是map一個數(shù)組然后返回一個ReactNode,為了方便react內(nèi)部進行優(yōu)化,我們必須給每一個reactNode添加key,這個key prop在設(shè)計值處不是給開發(fā)者用的,而是給react用的,大概的作用就是給每一個reactNode添加一個身份標(biāo)識,方便react進行識別,在重渲染過程中,如果key一樣,若組件屬性有所變化,則react只更新組件對應(yīng)的屬性;沒有變化則不更新,如果key不一樣,則react先銷毀該組件,然后重新創(chuàng)建該組件。
我現(xiàn)在有一個數(shù)組[1,2,3,4],請實現(xiàn)算法,得到這個數(shù)組的全排列的數(shù)組,如[2,1,3,4],[2,1,4,3]。。。。你這個算法的時間復(fù)雜度是多少
這個我沒寫出來,大概給了個思路,將每一個數(shù)組拆除倆個小數(shù)組進行求它的全排列,然后得到的結(jié)果互相之間又進行全排列,然后把最后的結(jié)果連接起來。。。
感興趣的同學(xué)見數(shù)組全排列
我現(xiàn)在有一個背包,容量為m,然后有n個貨物,重量分別為w1,w2,w3...wn,每個貨物的價值是v1,v2,v3...vn,w和v沒有任何關(guān)系,請求背包能裝下的最大價值。
這個我也沒寫出來,也給了個思路,首先使用Q4的方法得到貨物重量數(shù)組的全組合(包括拆分成小數(shù)組的全組合),然后計算每一個組合的價值,并進行排序,然后遍歷數(shù)組,找到價值較高切剛好能裝進背包m的組合。
本題 動態(tài)規(guī)劃面試題,感興趣的同學(xué)請自行百度或者谷歌。
四面
請說一下你的上一家公司的研發(fā)發(fā)布流程。
你說一下webpack的一些plugin,怎么使用webpack對項目進行優(yōu)化。
正好最近在做webpack構(gòu)建優(yōu)化和性能優(yōu)化的事兒,當(dāng)時吹了大概15~20分鐘吧,插件請見webpack插件歸納總結(jié)。
構(gòu)建優(yōu)化:
減少編譯體積 ContextReplacementPugin、IgnorePlugin、babel-plugin-import、
并行編譯 happypack、thread-loader、uglifyjsWebpackPlugin開啟并行
緩存 cache-loader、hard-source-webpack-plugin、uglifyjsWebpackPlugin開啟緩存、babel-loader開啟緩存
預(yù)編譯 dllWebpackPlugin && DllReferencePlugin、
性能優(yōu)化:
減少編譯體積 Tree-shaking、
緩存
拆包 splitChunksPlugin、import()、
的new實例和es5的new實例有什么區(qū)別
這個我覺得是一樣的(當(dāng)時因為很少看babel編譯之后的結(jié)果),面試官說不一樣。。。后來我看了一下babel的編譯結(jié)果,發(fā)現(xiàn)只是類的方法聲明的過程不一樣而已,最后new的結(jié)果是一樣的。。。具體答案現(xiàn)在我也不知道。。。
看你簡歷上寫了canvas,你說一下為什么canvas的圖片為什么過有跨域問題
圖片為什么跨域我不知道,至今沒查出來,也差不多,大概跨域原因和瀏覽器跨域的原因是一樣的吧。
我現(xiàn)在有一個canvas,上面隨機布著一些黑塊,請實現(xiàn)方法,計算canvas上有多少個黑塊
使用getImageData獲取像素數(shù)組,然后遍歷數(shù)組,把在遍歷節(jié)點的過程中,查看節(jié)點上下左右的像素顏色是否相同,如果相同,然后設(shè)置標(biāo)識,最后groupBy一下所有像素。(這是我當(dāng)時的方案)
其他更好的答案見地址
請手寫實現(xiàn)一個
這個就不寫了,詳情見promise實現(xiàn)原理
注:四面是一個超級可愛的小姐姐,電腦給我讓我寫完之后,我說我寫得差不多了,然后電腦給她,然后她竟然默默的在看我的代碼,嘗試尋找我的思路,也沒有問我實現(xiàn)思路是啥,然后我就問她,你不應(yīng)該是讓我給你解釋我的代碼思路嗎。。。你竟然在嘗試尋找我的思路,我自己都不知道我自己是思路是啥。。。然后我兩都笑了,哈哈哈。最后結(jié)束的時候我說我午飯還沒吃,她還叫了另外一個小哥哥先帶了下去吃飯,真是一個善良的小姐姐,非常感謝。
五面
你說一下你的技術(shù)有什么特點
說一下你覺得你最得意的一個項目?你這個項目有什么缺陷,弊端嗎?
現(xiàn)在有那么一個團隊,假如讓你來做技術(shù)架構(gòu),你會怎么做?
考慮到團隊每一個前端的技術(shù)??赡懿灰恢?,這個時候我可能選擇微前端架構(gòu),讓每個人負責(zé)的模塊可以單獨開發(fā),單獨部署,單獨回滾,不依賴于其他項目模塊,在盡可能的情況下節(jié)約團隊成員之間的學(xué)習(xí)成本,當(dāng)然這肯定也有缺點,那就是每個模塊都需要一個前端項目,單獨部署,單獨回滾無疑也加大了運維成本。
說一下你上一家公司的主要業(yè)務(wù)流程,你參與到其中了嗎?
杭州有贊
一面 WEB前端工程師 電話面 全程43分鐘
自我介紹
說說從輸入URL到看到頁面發(fā)生的全過程,越詳細越好
首先瀏覽器主進程接管,開了一個下載線程。
然后進行HTTP請求(DNS查詢、IP尋址等等),中間會有三次捂手,等待響應(yīng),開始下載響應(yīng)報文。
將下載完的內(nèi)容轉(zhuǎn)交給Renderer進程管理。
進程開始解析css rule tree和dom tree,這兩個過程是并行的,所以一般我會把link標(biāo)簽放在頁面頂部。
解析繪制過程中,當(dāng)瀏覽器遇到link標(biāo)簽或者script、img等標(biāo)簽,瀏覽器會去下載這些內(nèi)容,遇到時候緩存的使用緩存,不適用緩存的重新下載資源。
和dom tree生成完了之后,開始合成render tree,這個時候瀏覽器會進行l(wèi)ayout,開始計算每一個節(jié)點的位置,然后進行繪制。
繪制結(jié)束后,關(guān)閉TCP連接,過程有四次揮手。
你剛剛說了三次握手,四次揮手,那你描述一下?
本人對計算機網(wǎng)絡(luò)的這些概念一直不是很熟悉,所以這個問題回答不會,這里mark下文章,感興趣的同學(xué)查看地址
剛剛Q2中說的CSS和JS的位置會影響頁面效率,為什么?
在加載過程中不會影響到DOM樹的生成,但是會影響到Render樹的生成,進而影響到layout,所以一般來說,style的link標(biāo)簽需要盡量放在head里面,因為在解析DOM樹的時候是自上而下的,而css樣式又是通過異步加載的,這樣的話,解析DOM樹下的body節(jié)點和加載css樣式能盡可能的并行,加快Render樹的生成的速度。
腳本應(yīng)該放在底部,原因在于js線程與GUI渲染線程是互斥的關(guān)系,如果js放在首部,當(dāng)下載執(zhí)行js的時候,會影響渲染行程繪制頁面,js的作用主要是處理交互,而交互必須得先讓頁面呈現(xiàn)才能進行,所以為了保證用戶體驗,盡量讓頁面先繪制出來。
現(xiàn)在有一個函數(shù)A和函數(shù)B,請你實現(xiàn)B繼承
方式1function B(){}function A(){}B.prototype = new A();// 方式2function A(){}function B(){ A.call(this);}// 方式
剛剛你在Q5中說的幾種繼承的方式,分別說說他們的優(yōu)缺點
方式1:簡單易懂,但是無法實現(xiàn)多繼承,父類新增原型方法/原型屬性,子類都能訪問到
方式2:可以實現(xiàn)多繼承,但是只能繼承父類的實例屬性和方法,不能繼承原型屬性/方法
方式3:可以繼承實例屬性/方法,也可以繼承原型屬性/方法,但是示例了兩個A的構(gòu)造函數(shù)
說說CSS中幾種垂直水平居中的方式
參考前面百度一面筆試題
中說的flex布局,垂直水平居中必須知道寬度嗎?
是的,必須知道高度(腦子進水了回答了必須知道,其實答案是不需要知道高度的
描述一下
函數(shù)執(zhí)行的上下文,可以通過apply,call,bind改變this的指向。對于匿名函數(shù)或者直接調(diào)用的函數(shù)來說,this指向全局上下文(瀏覽器為window,nodejs為global),剩下的函數(shù)調(diào)用,那就是誰調(diào)用它,this就指向誰。當(dāng)然還有es6的箭頭函數(shù),箭頭函數(shù)的指向取決于該箭頭函數(shù)聲明的位置,在哪里聲明,this就指向哪里。
說一下瀏覽器的緩存機制
瀏覽器緩存機制有兩種,一種為強緩存,一種為協(xié)商緩存。
對于強緩存,瀏覽器在第一次請求的時候,會直接下載資源,然后緩存在本地,第二次請求的時候,直接使用緩存。
對于協(xié)商緩存,第一次請求緩存且保存緩存標(biāo)識與時間,重復(fù)請求向服務(wù)器發(fā)送緩存標(biāo)識和最后緩存時間,服務(wù)端進行校驗,如果失效則使用緩存。
協(xié)商緩存方案:
1:服務(wù)端的響應(yīng)頭,第一次請求的時候,告訴客戶端,該資源什么時候會過期。Exprires的缺陷是必須保證服務(wù)端時間和客戶端時間嚴格同步。
2:max-age,表示該資源多少時間后過期,解決了客戶端和服務(wù)端時間必須同步的問題,
3:緩存標(biāo)識,對比緩存時使用它來標(biāo)識一個緩存,第一次請求的時候,服務(wù)端會返回該標(biāo)識給客戶端,客戶端在第二次請求的時候會帶上該標(biāo)識與服務(wù)端進行對比并返回If-None-Match標(biāo)識是否表示匹配。
4:第一次請求的時候服務(wù)端返回Last-modified表明請求的資源上次的修改時間,第二次請求的時候客戶端帶上請求頭If-Modified-Since,表示資源上次的修改時間,服務(wù)端拿到這兩個字段進行對比。
是這個字符串是怎么生成的?
沒答出來,我當(dāng)時猜是根據(jù)文件內(nèi)容或者最后修改時間進行的加密算法。其實官方?jīng)]有明確指定生成ETag值的方法。 通常,使用內(nèi)容的散列,最后修改時間戳的哈希值,或簡單地使用版本號。
現(xiàn)在要你完成一個Dialog組件,說說你設(shè)計的思路?它應(yīng)該有什么功能?
該組件需要提供hook指定渲染位置,默認渲染在body下面。
然后改組件可以指定外層樣式,如寬度等
組件外層還需要一層mask來遮住底層內(nèi)容,點擊mask可以執(zhí)行傳進來的onCancel函數(shù)關(guān)閉Dialog。
另外組件是可控的,需要外層傳入visible表示是否可見。
然后Dialog可能需要自定義頭head和底部footer,默認有頭部和底部,底部有一個確認按鈕和取消按鈕,確認按鈕會執(zhí)行外部傳進來的onOk事件,然后取消按鈕會執(zhí)行外部傳進來的onCancel事件。
當(dāng)組件的visible為true時候,設(shè)置body的overflow為hidden,隱藏body的滾動條,反之顯示滾動條。
組件高度可能大于頁面高度,組件內(nèi)部需要滾動條。
只有組件的visible有變化且為ture時候,才重渲染組件內(nèi)的所有內(nèi)容。
你覺得你做過的你覺得最值得炫耀的項目?
螞蟻金服-體驗技術(shù)部 資深數(shù)據(jù)可視化研發(fā)工程師
一面 電話面 全程1小時24分鐘
描述一下你最近做的可視化的項目
剛剛說的java調(diào)用js離線生成數(shù)據(jù)報告?java調(diào)用js的promise異步返回結(jié)果怎么實現(xiàn)的?
使用java的js引擎Nashorn,Nashorn不支持事件隊列,是要引進polyfill,然后java調(diào)用js方法獲得java的promise對象,然后在調(diào)用該對象的then方法,回調(diào)函數(shù)為java中的某各類的某個方法,然后while一個表示是否已執(zhí)行回調(diào)的變量,如果未執(zhí)行,則讓java主線程sleep,如果已經(jīng)執(zhí)行,則跳出循環(huán),表示是否已執(zhí)行回調(diào)的變量在傳入promise的回調(diào)函數(shù)中設(shè)置更改。詳情代碼見地址
說說svg和canvas各自的優(yōu)缺點?
共同點:都是有效的圖形工具,對于數(shù)據(jù)較小的情況下,都很又高的性能,它們都使用 JavaScript 和 HTML;它們都遵守萬維網(wǎng)聯(lián)合會 (W3C) 標(biāo)準。
優(yōu)點:
矢量圖,不依賴于像素,無限放大后不會失真。
以dom的形式表示,事件綁定由瀏覽器直接分發(fā)到節(jié)點上。
缺點:
形式,涉及到動畫時候需要更新dom,性能較低。
優(yōu)點:
定制型更強,可以繪制繪制自己想要的東西。
非dom結(jié)構(gòu)形式,用JavaScript進行繪制,涉及到動畫性能較高。
缺點:
事件分發(fā)由canvas處理,繪制的內(nèi)容的事件需要自己做處理。
依賴于像素,無法高效保真,畫布較大時候性能較低。
你剛剛說的canvas渲染較大畫布的時候性能會較低?為什么?
因為canvas依賴于像素,在繪制過程中是一個一個像素去繪制的,當(dāng)畫布足夠大,像素點也就會足夠多,那么想能就會足夠低。
假設(shè)我現(xiàn)在有5000個圓,完全繪制出來,點擊某一個圓,該圓高亮,另外4999個圓設(shè)為半透明,分別說說用svg和canvas怎么實現(xiàn)?
首先,從數(shù)據(jù)出發(fā),我們的每個圓是一個數(shù)據(jù),這個數(shù)據(jù)有圓的x、y、radius、isHighlight如果是svg,直接渲染節(jié)點即可,然后往節(jié)點上邊綁定點擊事件,點擊改變所有數(shù)據(jù)的高亮屬性(必須同步執(zhí)行完成),然后讓瀏覽器進行繪制。如果是canvas,我們需要自己綁定事件到canvans標(biāo)簽上,然后點擊的時候判斷點擊的位置是否在圓內(nèi),如果在某個圓內(nèi),則更新所有數(shù)據(jù)的高亮屬性,之后在進行一次性繪制。
剛剛說的canvas的點擊事件,怎么樣實現(xiàn)?假如不是圓,這些圖形是正方形、長方形、規(guī)則圖形、不規(guī)則圖形呢
針對于每一個形狀,將其抽象成shape類,每一個類有自己的方法isPointInSide來判斷節(jié)點是否在圖形內(nèi),對于不規(guī)則圖形,當(dāng)做矩形處理,點擊的時候執(zhí)行該方法判斷點擊位置是否在圖形內(nèi)。
那假如我的圖形可能有變形、放大、偏移、旋轉(zhuǎn)的需求呢?你的這個isPointInSide怎么處理?
這個我答不出來,據(jù)面試官提示,好像有相應(yīng)的API處理變形、旋轉(zhuǎn)、放大等等之后的位置映射關(guān)系。
那個這個canvas的點擊事件,點擊的時候怎么樣快速的從這5000個圓中找到你點擊的那個圓(不完全遍歷5000個節(jié)點)?
可以通過預(yù)查找的形式,當(dāng)鼠標(biāo)劃過的時候預(yù)先查找到鼠標(biāo)附近的一些節(jié)點,當(dāng)點擊的時候在從這些預(yù)先篩選好的節(jié)點里查找點擊下來的節(jié)點,當(dāng)然這個方法的前提是不能影響js主線程的執(zhí)行,必須是異步的形式。
那你用過@antv/g6,里面有一個tree,說說你大學(xué)時候接觸到的tree的數(shù)據(jù)結(jié)構(gòu)是怎么實現(xiàn)的?
畢業(yè)一年多,tree的結(jié)構(gòu)大概忘記了,我當(dāng)時是這么回答的:
大學(xué)使用的是C++學(xué)的數(shù)據(jù)結(jié)構(gòu),是用指針的形式,首先有一個根節(jié)點,根節(jié)點里有一個指針數(shù)組指向它的所有子節(jié)點,然后每一個子節(jié)點也是,擁有著子節(jié)點的指針數(shù)組,一層一層往下,直到為葉子節(jié)點,指針數(shù)組指向為空。
還記得二叉樹嗎?描述二叉樹的幾種遍歷方式?
先序遍歷:若二叉樹非空,訪問根結(jié)點,遍歷左子樹,遍歷右子樹。
中序遍歷:若二叉樹非空,遍歷左子樹;訪問根結(jié)點;遍歷右子樹。
后序遍歷:若二叉樹非空,遍歷左子樹;遍歷右子樹;訪問根結(jié)點。
所有遍歷是以遞歸的形似,直到?jīng)]有子節(jié)點。
說說你記得的所有的排序,他們的原理是什么?
冒泡排序:雙層遍歷,對比前后兩個節(jié)點,如果滿足條件,位置互換,直到遍歷結(jié)束。
快速排序:去數(shù)組中間的那一個數(shù),然后遍歷所有數(shù),小于該數(shù)的push到一個數(shù)組,大于該數(shù)的push到另外一個數(shù)組,然后遞歸去排序這兩個數(shù)組,最后將所有結(jié)果連接起來。
選擇排序:聲明一個數(shù)組,每次去輸入數(shù)組里面找數(shù)組中的最大值或者最小值,取出來后push到聲明的數(shù)組中,直到輸入數(shù)組為空。
說一下你覺得你做過的最復(fù)雜的項目?中間遇到的困難,以及你是怎么解決的?
面試官:我這邊問題差不多問完了,你還有什么問題?
我:很驚訝今天全都是問可視化相關(guān)的,沒怎么問js,css,html。
面試官:那我們繼續(xù)吧
我:。。。
那給我介紹一下react吧(面試官是做可視化開發(fā)的,根本不懂react)
以前我們沒有jquery的時候,我們大概的流程是從后端通過ajax獲取到數(shù)據(jù)然后使用jquery生成dom結(jié)果然后更新到頁面當(dāng)中,但是隨著業(yè)務(wù)發(fā)展,我們的項目可能會越來越復(fù)雜,我們每次請求到數(shù)據(jù),或則數(shù)據(jù)有更改的時候,我們又需要重新組裝一次dom結(jié)構(gòu),然后更新頁面,這樣我們手動同步dom和數(shù)據(jù)的成本就越來越高,而且頻繁的操作dom,也使我我們頁面的性能慢慢的降低。
這個時候mvvm出現(xiàn)了,mvvm的雙向數(shù)據(jù)綁定可以讓我們在數(shù)據(jù)修改的同時同步dom的更新,dom的更新也可以直接同步我們數(shù)據(jù)的更改,這個特定可以大大降低我們手動去維護dom更新的成本,mvvm為react的特性之一,雖然react屬于單項數(shù)據(jù)流,需要我們手動實現(xiàn)雙向數(shù)據(jù)綁定。
有了mvvm還不夠,因為如果每次有數(shù)據(jù)做了更改,然后我們都全量更新dom結(jié)構(gòu)的話,也沒辦法解決我們頻繁操作dom結(jié)構(gòu)(降低了頁面性能)的問題,為了解決這個問題,react內(nèi)部實現(xiàn)了一套虛擬dom結(jié)構(gòu),也就是用js實現(xiàn)的一套dom結(jié)構(gòu),他的作用是講真實dom在js中做一套緩存,每次有數(shù)據(jù)更改的時候,react內(nèi)部先使用算法,也就是鼎鼎有名的diff算法對dom結(jié)構(gòu)進行對比,找到那些我們需要新增、更新、刪除的dom節(jié)點,然后一次性對真實DOM進行更新,這樣就大大降低了操作dom的次數(shù)。
那么diff算法是怎么運作的呢,首先,diff針對類型不同的節(jié)點,會直接判定原來節(jié)點需要卸載并且用新的節(jié)點來裝載卸載的節(jié)點的位置;針對于節(jié)點類型相同的節(jié)點,會對比這個節(jié)點的所有屬性,如果節(jié)點的所有屬性相同,那么判定這個節(jié)點不需要更新,如果節(jié)點屬性不相同,那么會判定這個節(jié)點需要更新,react會更新并重渲染這個節(jié)點。
設(shè)計之初是主要負責(zé)UI層的渲染,雖然每個組件有自己的state,state表示組件的狀態(tài),當(dāng)狀態(tài)需要變化的時候,需要使用setState更新我們的組件,但是,我們想通過一個組件重渲染它的兄弟組件,我們就需要將組件的狀態(tài)提升到父組件當(dāng)中,讓父組件的狀態(tài)來控制這兩個組件的重渲染,當(dāng)我們組件的層次越來越深的時候,狀態(tài)需要一直往下傳,無疑加大了我們代碼的復(fù)雜度,我們需要一個狀態(tài)管理中心,來幫我們管理我們狀態(tài)state。
這個時候,redux出現(xiàn)了,我們可以將所有的state交給redux去管理,當(dāng)我們的某一個state有變化的時候,依賴到這個state的組件就會進行一次重渲染,這樣就解決了我們的我們需要一直把state往下傳的問題。redux有action、reducer的概念,action為唯一修改state的來源,reducer為唯一確定state如何變化的入口,這使得redux的數(shù)據(jù)流非常規(guī)范,同時也暴露出了redux代碼的復(fù)雜,本來那么簡單的功能,卻需要完成那么多的代碼。
后來,社區(qū)就出現(xiàn)了另外一套解決方案,也就是mobx,它推崇代碼簡約易懂,只需要定義一個可觀測的對象,然后哪個組價使用到這個可觀測的對象,并且這個對象的數(shù)據(jù)有更改,那么這個組件就會重渲染,而且mobx內(nèi)部也做好了是否重渲染組件的生命周期shouldUpdateComponent,不建議開發(fā)者進行更改,這使得我們使用mobx開發(fā)項目的時候可以簡單快速的完成很多功能,連redux的作者也推薦使用mobx進行項目開發(fā)。但是,隨著項目的不斷變大,mobx也不斷暴露出了它的缺點,就是數(shù)據(jù)流太隨意,出了bug之后不好追溯數(shù)據(jù)的流向,這個缺點正好體現(xiàn)出了redux的優(yōu)點所在,所以針對于小項目來說,社區(qū)推薦使用mobx,對大項目推薦使用redux。
假如我一個組件有一個狀態(tài)count為1,然后我在componentDidMount()里面執(zhí)行執(zhí)行了兩次this.setState({count: this.state.count++}),然后又執(zhí)行了兩次setTimeout(() => { this.setState({count: this.state.count++}) }, 0),最后count為多少?為什么?
為4,因為第二次執(zhí)行setState的時候,取不到第一次this.state.count++的結(jié)果,react在一輪生命周期結(jié)束后才會更新內(nèi)部的state,如果在一輪生命周期內(nèi)多次使用了setState,react內(nèi)部會有一個字段isBatchUpdate標(biāo)識本次更新為批量更新,然后在最后render的時候?qū)⑺衧etState的結(jié)果提交到state中,一次性進行更新,并且把isBatchUpdate這個字段設(shè)置為false。
針對于兩次setTimeout,js引擎會把這兩個setState丟到事件隊列中,等待js空閑了去執(zhí)行,而我們的渲染函數(shù)render是同步執(zhí)行的(react16版本默認沒有開啟異步渲染),所以等我們render執(zhí)行完全,也就是我們的state被同步完后,在取事件隊列里面的setState進行執(zhí)行,setTimeout的第二個setState也是一樣的,所以最后結(jié)果是4。
說一下你覺得你做過的最值得你說的吧
最后
這幾輪面試的面試官都非常和藹好交流,百度的五輪面試不知道過了沒有,只記得五面的面試官說,你稍等一下,我去問一下其他人對你還有什么其他要求,然后過了一會兒HR就喊我先回去了,叫我等HR面的消息,如果沒通過,也不會在聯(lián)系我了,已經(jīng)過了四天了,但愿后面有消息吧。然后有贊、螞蟻金服的兩個一面都過了,因為每次面完試面試官問我還有什么問題嗎?我都會詢問一下本次面試面試官對我的評論是啥。