大神們能夠寫出「深刻淺出」系列,小白就寫點"真·淺嘗輒止"系列的吧,主要便於本身理解和鞏固,畢竟一開始就上源碼仍是會頭大滴,因而就準備淺嘗輒止的瞭解下"React是如何工做的?"javascript
React是怎麼工做的? 你知道Diff算法嗎 ---xx面試官html
小白的前端排坑指南:前端
1.VK的秋招前端奇遇記(一)java
2.VK的秋招前端奇遇記(二)react
4.番外篇:前端面試&筆試算法 Algorithmgithub
React除了是MVC框架,數據驅動頁面的特色以外,核心的就是他很"快"。 按照廣泛的說法:"由於直接操做DOM會帶來重繪、迴流等,帶來巨大的性能損耗而致使渲染慢等問題。React使用了虛擬DOM,每次狀態更新,React比較虛擬DOM的差別以後,再更改變化的內容,最後統一由React去修改真實DOM、完成頁面的更新、渲染。"面試
上面這段話,是咱們都會說的,那麼通常到這裏,面試官就問了:"什麼是虛擬DOM,React是怎麼進行比較的?Diff算法瞭解嗎?"。以前是有點崩潰的,因而決定淺嘗一下:算法
DOM沒什麼好說的,主要說下虛擬DOM的一些特色:數據庫
前幾點沒什麼好說的,注意第四點,也就是你每個改動,每個動做都會讓React去根據當前的狀態建立一個全新的Virtual DOM。
這裏每當Virtual DOM生成,都打印了出來,能夠看到,它表明着真實DOM,而每次生成全新的,也是爲了可以比較old dom和new dom以前的差異。
剛纔提到了,React會抓取每一個狀態下的內容,生成一個全新的Virtual DOM,而後經過和前一個的比較,找出不一樣和差別。React的Diff算法有兩個約定:
第二點着重說一下,舉個例子:好比真實DOM的ul標籤下,有一系列的<li>
標籤,然而當你想要從新排列這個標籤時,若是你給了每一個標籤一個key
值,React在比較差別的時候,就可以知道"你仍是你,只不過位置變化了"。 React除了要最快的找到差別外,還但願變化是最小的。若是加了key
,react就會保留實例,而不像以前同樣,徹底創造一個全新的DOM。
來個更具體的:
1234
下一個狀態後,序列變爲
1243
對於咱們來說,其實就是調換了4和3的順序。但是怎麼讓React知道,原來的那個3
跑到了原來的4
後面了呢? 就是這個惟一的key
起了做用。
相關面試題:爲何React中列表模板中要加入key
Diff在進行比較的時候,首先會比較兩個根元素,當差別是類型的改變的時候,可能就要花更多的「功夫」了
好比如今狀態有這樣的一個改變:
<div>
<Counter />
</div>
-----------
<span>
<Counter />
</span>
複製代碼
能夠看到,從<div>
變成了<span>
,這種類型的改變,帶來的是直接對old tree的總體摧毀,包括子元素Counter
。 因此舊的實例Counter
會被徹底摧毀後,建立一個新的實例來,顯然這種效率是低下的
當比較後發現兩個是同類型的,那好辦了,React會查看其屬性的變化,而後直接修改屬性,原來的實例都得意保留,從而使得渲染高效,好比:
<div className="before" title="stuff" />
<div className="after" title="stuff" />
複製代碼
除了className
,包括style
也是同樣,增長、刪除、修改都不會對整個 dom tree進行摧毀,而是屬性的修改,保留其下面元素和節點
與上面相似,相同類型的組件元素,子元素的實力會保持,不會摧毀。 當組件更新時,實例保持不變,以便在渲染之間保持狀態。React更新底層組件實例的props以匹配新元素,並在底層實例上調用componentWillReceiveProps()
和componentWillUpdate()
。
接下來,調用render()
方法,diff算法對前一個結果和新結果進行遞歸
若是前面對key
的解釋,還不夠清除,這裏用一個真正的實例來講明key
的重要性吧。
<ul>
<li>first</li>
<li>second</li>
</ul>
------
<ul>
<li>first</li>
<li>second</li>
<li>third</li>
</ul>
複製代碼
能夠看到,在這種狀況下,React只須要在最後insert
一個新元素便可,其餘的都不須要變化,這個時候React是高效的,可是若是在場景二下:
<ul>
<li>Duke</li>
<li>Villanova</li>
</ul>
---
<ul>
<li>Connecticut</li>
<li>Duke</li>
<li>Villanova</li>
</ul>
複製代碼
這對React可能就是災難性的,由於React只知道前兩個元素不一樣,所以會徹底創新一個新的元素,最後致使三個元素都是從新建立的,這大大下降了效率。這個時候,key
就排上用場了。當子元素有key
時,React使用key
將原始樹中的子元素與後續樹中的子元素相匹配。例如,在上面的低效示例中添加一個key
可使樹轉換更高效:
<ul>
<li key="2015">Duke</li>
<li key="2016">Villanova</li>
</ul>
------
<ul>
<li key="2014">Connecticut</li>
<li key="2015">Duke</li>
<li key="2016">Villanova</li>
</ul>
複製代碼
這樣,只有key
值爲2014的是新建立的,而2015
和2016
僅僅是移動了位置而已。
React是用什麼策略來比較兩顆tree之間的差別呢?這個策略是最核心的部分:
兩個樹的徹底的 diff 算法是一個時間複雜度爲 O(n^3) 的問題。可是在前端當中,你不多會跨越層級地移動DOM元素。因此 Virtual DOM 只會對同一個層級的元素進行對比:
上面的div
只會和同一層級的div
對比,第二層級的只會跟第二層級對比。這樣算法複雜度就能夠達到 O(n)。
在實際代碼中,會對新舊兩棵樹進行一個深度優先的遍歷,這樣每一個節點都會有一個惟一的標記,而後記錄差別
在深度優先遍歷的時候,每遍歷到一個節點就把該節點和新的的樹進行對比。若是有差別的話就記錄到一個對象裏面。
好比第上圖中的1號節點p,有了變化,這樣子就記錄以下:
patches[1] = [{difference}, {difference}...]//用數組存儲新舊節點的差別
複製代碼
ok,那麼差別類型呢,在上一節中已經說了,包括根元素的類型的不一樣分爲兩大類,而後根據不一樣的狀況採起不一樣的更換策略。
最後,就是在真實DOM進行操做,apply這些差別,更新和渲染了。
這又是一個很厲害的問題了,使用Redux的都知道,reducers會接收上一個state
和action
做爲參數,而後返回一個新的state
,這個新的state
不能是在原來state
基礎上的修改。因此常常能夠看到如下的寫法:
return Object.assign(...)
//或者----------
return {...state,xx:xxx}
複製代碼
其做用,都是爲了返回一個全新的對象。
爲何reducers要求是純函數(返回全新的對象,不影響原對象)? --某面試官
從本質上講,純函數的定義以下:不修改函數的輸入值,依賴於外部狀態(好比數據庫,DOM和全局變量),同時對於任何相同的輸入有着相同的輸出結果。
舉個例子,下面的add函數不修改變量a或b,同時不依賴外部狀態,對於相同的輸入始終返回相同的結果。
const add = (a,b) => {a + b};
複製代碼
這就是一個純函數,結果對a、b沒有任何影響,回頭去看reducer,它符合純函數的全部特徵,因此就是一個純函數
先告訴你結果吧,若是在reducer中,在原來的state
上進行操做,並返回的話,並不會讓React從新渲染。 徹底不會有任何變化!
接下來看下Redux的源碼:
Redux接收一個給定的state(對象),而後經過循環將state的每一部分傳遞給每一個對應的reducer。若是有發生任何改變,reducer將返回一個新的對象。若是不發生任何變化,reducer將返回舊的state。
Redux只經過比較新舊兩個對象的存儲位置來比較新舊兩個對象是否相同。若是你在reducer內部直接修改舊的state對象的屬性值,那麼新的state和舊的state將都指向同一個對象。所以Redux認爲沒有任何改變,返回的state將爲舊的state。
好了,也就是說,從源碼的角度來說,redux要求開發者必須讓新的state
是全新的對象。那麼爲何非要這麼麻煩開發者呢?
請看下面的例子:嘗試比較a和b是否相同
var a = {
name: 'jack',
friend: ['sam','xiaoming','cunsi'],
years: 12,
...//省略n項目
}
var b = {
name: 'jack',
friend: ['sam','xiaoming','cunsi'],
years: 13,
...//省略n項目
}
複製代碼
思路是怎樣的?咱們須要遍歷對象,若是對象的屬性是數組,還須要進行遞歸遍歷,去看內容是否一致、是否發生了變化。 這帶來的性能損耗是很是巨大的。 有沒有更好的辦法?
有!
//接上面的例子
a === b //false
複製代碼
我不要進行深度比較,只是淺比較,引用值不同(不是同一個對象),那就是不同的。 這就是redux
的reducer
如此設計的緣由了