国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

React 是怎樣煉成的

lijinke666 / 1334人閱讀

摘要:唯一不足的是,這種開發方式容易造成注入等安全問題。其中,最棘手的是如何再現中的更新機制。換句話來說,節點是包含狀態的。對于沒有改變的節點,讓它保持原樣不動,僅僅創建并替換變更過的節點。是樹形結構,所以算法必須是針對樹形結構的。

本文主要講述 React 的誕生過程和優化思路。

內容整理自 2014 年的 OSCON - React Architecture by vjeux,雖然從今天(2018)來看可能會有點歷史感,但仍然值得學習了解。以史為鑒,從中也可以管窺 Facebook 優秀的工程管理文化。

字符拼接時代 - 2004

時間回到 2004 年,Mark Zuckerberg 當時還在宿舍搗鼓最初版的 Facebook 。
這一年,大家都在用 PHP 的字符串拼接(String Concatenation)功能來開發網站。

$str = "
    "; foreach ($talks as $talk) { $str += "
  • " . $talk->name . "
  • "; } $str += "
";

這種網站開發方式在當時看來是非常正確的,因為不管是后端開發還是前端開發,甚至根本沒有開發經驗,都可以使用這種方式搭建一個大型網站。

唯一不足的是,這種開發方式容易造成 XSS 注入等安全問題。如果 $talk->name 中包含惡意代碼,而又沒有做任何防護措施的話,那么攻擊者就可以注入任意 JS 代碼。于是就催生了“永遠不要相信用戶的輸入”的安全守則。

最簡單的應對方法是對用戶的任何輸入都進行轉義(Escape)。然而這也帶來了其他麻煩,如果對字符串進行多次轉義,那么反轉義的次數也必須是相同的,否則會無法得到原內容。如果又不小心把 HTML 標簽(Markup)給轉義了,那么 HTML 標簽會直接顯示給用戶,從而導致很差的用戶體驗。

XHP 時代 - 2010

到了 2010 年,為了更加高效的編碼,同時也避免轉義 HTML 標簽的錯誤,Facebook 開發了 XHP 。XHP 是對 PHP 的語法拓展,它允許開發者直接在 PHP 中使用 HTML 標簽,而不再使用字符串。

$content = 
    ; foreach ($talks as $talk) { $content->appendChild(
  • {$talk->name}
  • ); }

這樣的話,所有的 HTML 標簽都使用不同于 PHP 的語法,我們可以輕易的分辨哪些需要轉義哪些不需要轉義。

不久的后來,Facebook 的工程師又發現他們還可以創建自定義標簽,而且通過組合自定義標簽有助于構建大型應用。
而這恰恰是 Semantic Web 和 Web Components 概念的一種實現方式。

$content = ;
foreach ($talks as $talk) {
  $content->appendChild();
}

之后,Facebook 在 JS 中嘗試了更多的新技術方式以減小客戶端和服務端之間的延時。比如跨瀏覽器 DOM 庫和數據綁定,但是都不是很理想。

JSX - 2013

等到 2013 年,突然有一天,前端工程師 Jordan Walke 向他的經理提出了一個大膽的想法:把 XHP 的拓展功能遷移到 JS 中。最開始大家都以為他瘋了,因為這與當時大家都看好的 JS 框架格格不入。不過他最終還是執著地說服了經理,允許他用 6 個月的時間來驗證這個想法。這里不得不說 Facebook 良好的工程師管理哲學讓人敬佩,值得借鑒。

附:Lee Byron 談 Facebook 工程師文化:Why Invest in Tools

要想把 XHP 的拓展功能遷移到 JS ,首要任務是需要一個拓展來讓 JS 支持 XML 語法,該拓展稱為 JSX 。當時,隨著 Node.js 的興起,Facebook 內部對于轉換 JS 已經有相當多的工程實踐了。所以實現 JSX 簡直輕而易舉,僅僅花費了大概一周的時間。

const content = (
  
    { talks.map(talk => )}
  
);
React

自此,開始了 React 的萬里長征,更大的困難還在后頭。其中,最棘手的是如何再現 PHP 中的更新機制。

在 PHP 中,每當有數據改變時,只需要跳到一個由 PHP 全新渲染的新頁面即可。
從開發者的角度來看的話,這種方式開發應用是非常簡單的,因為它不需要擔心變更,且界面上用戶數據改變時所有內容都是同步的。
只要有數據變更,就重新渲染整個頁面。

雖然簡單粗暴,但是這種方式的缺點也尤為突出,那就是它非常慢

“You need to be right before being good”,意思是說,為了驗證遷移方案的可行性,開發者必須快速實現一個可用版本,暫時不考慮性能問題。

DOM

取自于 PHP 的靈感,在 JS 中實現重新渲染的最簡單辦法是:當任何內容改變時,都重新構建整個 DOM,然后用新 DOM 取代舊 DOM 。

這種方式是可以工作的,但在有些場景下不適用。
比如它會失去當前聚焦的元素和光標,以及文本選擇和頁面滾動位置,這些都是頁面的當前狀態。
換句話來說,DOM 節點是包含狀態的

既然包含狀態,那么記下舊 DOM 的狀態然后在新 DOM 上還原不就行了么?
但是非常不幸,這種方式不僅實現起來復雜而且也無法覆蓋所有情況。

在 OSX 電腦上滾動頁面時,會伴隨著一定的滾動慣性。但是 JS 并沒有提供相應的 API 來讀取或者寫入滾動慣性。
對包含 iframe 的頁面來說,情況則更復雜。如果它來自其他域,那么瀏覽器安全策略限制根本不會允許我們查看其內部的內容,更不用說還原了。
因此可以看出,DOM 不僅僅有狀態,它還包含隱藏的、無法觸達的狀態

既然還原狀態行不通,那就換一種方式繞過去。
對于沒有改變的 DOM 節點,讓它保持原樣不動,僅僅創建并替換變更過的 DOM 節點。
這種方式實現了 DOM 節點復用(Reuse)。

至此,只要能夠識別出哪些節點改變了,那么就可以實現對 DOM 的更新。于是問題就轉化為如何比對兩個 DOM 的差異

Diff

說到對比差異,相信大家馬上就能聯想到版本控制(Version Control)。它的原理很簡單,記錄多個代碼快照,然后使用 diff 算法比對前后兩個快照,從而生成一系列諸如“刪除 5 行”、“新增 3 行”、“替換單詞”等的改動;通過把這一系列的改動應用到先前的代碼快照就可以得到之后的代碼快照。

而這正是 React 所需要的,只不過它的處理對象是 DOM 而不是文本文件。
難怪有人說:“I tend to think of React as Version Control for the DOM” 。

DOM 是樹形結構,所以 diff 算法必須是針對樹形結構的。目前已知的完整樹形結構 diff 算法復雜度為 O(n^3) 。

假如頁面中有 10,000 個 DOM 節點,這個數字看起來很龐大,但其實并不是不可想象。為了計算該復雜度的數量級大小,我們還假設在一個 CPU 周期我們可以完成單次對比操作(雖然不可能完成),且 CPU 主頻為 1 GHz 。這種情況下,diff 要花費的時間如下:

整整有 17 分鐘之長,簡直無法想象!

雖然說驗證階段暫不考慮性能問題,但是我們還是可以簡單了解下該算法是如何實現的。

附:完整的 Tree diff 實現算法。

新樹上的每個節點與舊樹上的每個節點對比

如果父節點相同,繼續循環對比子樹

在上圖的樹中,依據最小操作原則,可以找到三個嵌套的循環對比。

但如果認真思考下,其實在 Web 應用中,很少有移動一個元素到另一個地方的場景。一個例子可能的是拖拽(Drag)并放置(Drop)元素到另一個地方,但它并不常見。

唯一的常用場景是在子元素之間移動元素,例如在列表中新增、刪除和移動元素。既然如此,那可以僅僅對比同層級的節點。

如上圖所示,僅對相同顏色的節點做 diff ,這樣能把時間復雜度降到了 O(n^2) 。

key

針對同級元素的比較,又引入了另一個問題。
同層級元素名稱不同時,可以直接識別為不匹配;相同時,卻沒那么簡單了。
假如在某個節點下,上一次渲染了三個 ,然后下一次渲染變成了兩個。此時 diff 的結果會是什么呢?

最直觀的結果是前面兩個保持不變,刪除第三個。
當然,也可以刪除第一個同時保持最后兩個。
如果不嫌麻煩,還可以把舊的三個都刪除,然后新增兩個新元素。
這說明,對于相同標簽名稱的節點,我們沒有足夠信息來對比前后差異。

如果再加上元素的屬性呢?比如 value ,如果前后兩次標簽名稱和 value 屬性都相同,那么就認為元素匹配中,無須改動。但現實是這行不通,因為用戶輸入時值總是在變,會導致元素一直被替換,導致失去焦點;;更糟糕的是,并不是所有 HTML 元素都有這個屬性。

那使用所有元素都有的 id 屬性呢?這是可以的,如上圖,我們可以容易的識別出前后 DOM 的差異。考慮表單情況,表單模型的輸入通常跟 id 關聯,但如果使用 AJAX 來提交表單的話,我們通常不會給 input 設置 id 屬性。因此,更好的辦法是引入一個新的屬性名稱,專門用來輔助 diff 算法。這個屬性最終確定為 key 。這也是為什么在 React 中使用列表時會要求給子元素設置 key 屬性的原因。

結合 key ,再加上哈希表,diff 算法最終實現了 O(n) 的最優復雜度。
至此,可以看到從 XHP 遷移到 JS 的方案可行的。接下來就可以針對各個環節進行逐步優化。

附:詳細的 diff 理解:不可思議的 react diff 。
持續優化 Virtual DOM

前面說到,React 其實實現了對 DOM 節點的版本控制。
做過 JS 應用優化的人可能都知道,DOM 是復雜的,對它的操作(尤其是查詢和創建)是非常慢非常耗費資源的。看下面的例子,僅創建一個空白的 div,其實例屬性就達到 231 個。

// Chrome v63
const div = document.createElement("div");
let m = 0;
for (let k in div) {
  m++;
}
console.log(m); // 231

之所以有這么多屬性,是因為 DOM 節點被用于瀏覽器渲染管道的很多過程中。
瀏覽器首先根據 CSS 規則查找匹配的節點,這個過程會緩存很多元信息,例如它維護著一個對應 DOM 節點的 id 映射表。
然后,根據樣式計算節點布局,這里又會緩存位置和屏幕定位信息,以及其他很多的元信息,瀏覽器會盡量避免重新計算布局,所以這些數據都會被緩存。
可以看出,整個渲染過程會耗費大量的內存和 CPU 資源。

現在回過頭來想想 React ,其實它只在 diff 算法中用到了 DOM 節點,而且只用到了標簽名稱和部分屬性。
如果用更輕量級的 JS 對象來代替復雜的 DOM 節點,然后把對 DOM 的 diff 操作轉移到 JS 對象,就可以避免大量對 DOM 的查詢操作。這種方式稱為 Virtual DOM 。

其過程如下:

維護一個使用 JS 對象表示的 Virtual DOM,與真實 DOM 一一對應

對前后兩個 Virtual DOM 做 diff ,生成變更(Mutation)

把變更應用于真實 DOM,生成最新的真實 DOM

可以看出,因為要把變更應用到真實 DOM 上,所以還是避免不了要直接操作 DOM ,但是 React 的 diff 算法會把 DOM 改動次數降到最低。

至此,React 的兩大優化:diff 算法和 Virtual DOM ,均已完成。再加上 XHP 時代嘗試的數據綁定,已經算是一個可用版本了。
這個時候 Facebook 做了個重大的決定,那就是把 React 開源!

React 的開源可謂是一石激起千層浪,社區開發者都被這種全新的 Web 開發方式所吸引,React 因此迅速占領了 JS 開源庫的榜首。
很多大公司也把 React 應用到生產環境,同時也有大批社區開發者為 React 貢獻了代碼。

接下來要說的兩大優化就是來自于開源社區。

批處理(Batching)

著名瀏覽器廠商 Opera 把重排和重繪(Reflow and Repaint)列為影響頁面性能的三大原因之一。

我們說 DOM 是很慢的,除了前面說到的它的復雜和龐大,還有另一個原因就是重排和重繪。

當 DOM 被修改后,瀏覽器必須更新元素的位置和真實像素;
當嘗試從 DOM 讀取屬性時,為了保證讀取的值是正確的,瀏覽器也會觸發重排和重繪。
因此,反復的“讀取、修改、讀取、修改...”操作,將會觸發大量的重排和重繪。

另外,由于瀏覽器本身對 DOM 操作進行了優化,比如把兩次很近的“修改”操作合并成一個“修改”操作。
所以如果把“讀取、修改、讀取、修改...”重新排列為“讀取、讀取...”和“修改、修改...”,會有助于減小重排和重繪的次數。但是這種刻意的、手動的級聯寫法是不安全的。

與此同時,常規的 JS 寫法又很容易觸發重排和重繪。
在減小重排和重繪的道路上,React 陷入了尷尬的處境。

最終,社區貢獻者 Ben Alpert 使用批處理的方式拯救了這個尷尬的處境。

在 React 中,開發者通過調用組件的 setState 方法告訴 React 當前組件要變更了。

Ben Alpert 的做法是,調用 setState 時不立即把變更同步到 Virtual DOM,而是僅僅把對應元素打上“待更新”的標記。如果組件內調用多次 setState ,那么都會進行相同的打標操作。

等到初始化事件被完全廣播開以后,就開始進行從頂部到底部的重新渲染(Re-Render)過程。這就確保了 React 只對元素進行了一次渲染。

這里要注意兩點:

此處的重新渲染是指把 setState 變更同步到 Virtual DOM ;在這之后才進行 diff 操作生成真實的 DOM 變更。

與前文提到的“重新渲染整個 DOM ”不同的是,真實的重新渲染僅渲染被標記的元素及其子元素,也就是說上圖中僅藍色圓圈代表的元素會被重新渲染

這也提醒開發者,應該讓擁有狀態的組件盡量靠近葉子節點,這樣可以縮小重新渲染的范圍。

裁剪(Pruning)

隨著應用越來越大,React 管理的組件狀態也會越來越多,這就意味著重新渲染的范圍也會越來越大。

認真觀察上面批處理的過程可以發現,該 Virtual DOM 右下角的三個元素其實是沒有變更的,但是因為其父節點的變更也導致了它們的重新渲染,多做了無用操作。

對于這種情況,React 本身已經考慮到了,為此它提供了 bool shouldComponentUpdate(nextProps, nextState) 接口。開發者可以手動實現該接口來對比前后狀態和屬性,以判斷是否需要重新渲染。這樣的話,重新渲染就變成如下圖所示過程。

當時,React 雖然提供了 shouldComponentUpdate 接口,但是并沒有提供一個默認的實現方案(總是渲染),開發者必須自己手動實現才能達到預期效果。

其原因是,在 JS 中,我們通常使用對象來保存狀態,修改狀態時是直接修改該狀態對象的。也就是說,修改前后的兩個不同狀態指向了同一個對象,所以當直接比較兩個對象是否變更時,它們是相同的,即使狀態已經改變。

對此,David Nolen 提出了基于不可變數據結構(Immutable Data Structure)的解決方案。
該方案的靈感來自于 ClojureScript ,在 ClojureScript 中,大部分的值都是不可變的。換句話說就是,當需要更新一個值時,程序不是去修改原來的值,而是基于原來的值創建一個新值,然后使用新值進行賦值。

David 使用 ClojureScript 寫了一個針對 React 的不可變數據結構方案:Om ,為 shouldComponentUpdate 提供了默認實現。

不過,由于不可變數據結構并未被 Web 工程師廣為接受,所以當時并未把這項功能合并進 React 。
遺憾的是,截止到目前,shouldComponentUpdate 也仍然未提供默認實現。
但是 David 卻為廣大開發者開啟了一個很好的研究方向。

如果真想利用不可變數據結構來提高 React 性能,可以參考與 React 師出同門的 Facebook Immutable.js,它是 React 好搭檔!

結束語

React 的優化仍在繼續,比如 React 16 中新引入 Fiber,它是對核心算法的一次重構,即重新設計了檢測變更的方法和時機,允許渲染過程可以分段完成,而不必一次性完成。
受篇幅限制,本文不會深入介紹 Fiber ,有興趣的可以參考 React Fiber是什么 。

最后,感謝 Facebook 給開源社區帶來了如此優秀的項目!

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/107612.html

相關文章

  • [到codewars打怪獸]利潤怎樣成的

    摘要:利潤是怎樣煉成的怪獸的屬性怪獸的技能大木博士的圖鑒你是趙老爺家的算帳二狗子,趙家老爺想要學習一些理財知識,就去詢問孔乙己。 [7 kyu]Money, Money, Money 利潤是怎樣煉成的??2016.03.15 怪獸的屬性: showImg(http://ww2.sinaimg.cn/large/006m2mhTgw1f1xxc38fbqj30qa0s2q6f.jpg);sho...

    zhangyucha0 評論0 收藏0
  • 年薪50w+的軟件測試工程師怎么成的

    摘要:它讓傳統的測試工程師從簡單,重復,低效可替代性強的手工測試,變成了有技術難度和門檻的測試開發工作,也讓我們有更多的機會拿到更高的薪資。 隨著互聯網行業的迅速發展,軟件測試工程師的地位越來越高,公司招聘時的薪資也越來越高,那么市場上為什么還有大量的軟件測試工程師薪資只有5-6k呢?因為他們有一...

    laznrbfe 評論0 收藏0
  • 一場穩定、高清、流暢的大型活動直播怎么成的

    摘要:據悉,高清直播已在阿里云的眾多游戲直播客戶中廣泛使用。這也是阿里云視頻云第四年支持雙貓晚網絡直播,從作戰室監控的數據上來看,貓晚直播期間各項系統數據指標運轉平穩,一場穩定高清流暢的大型活動直播就就此實現。 雙11貓晚是家喻戶曉的綜藝晚會,在今年的雙11,阿里集團為2500萬用戶提供了一場在線直播視覺盛宴。網友評價這是一場既穩定流暢又高清的直播,當然在這背后離不開阿里云的技術支持。 本次...

    mist14 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<