日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關咨詢
選擇下列產品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關閉右側工具欄

新聞中心

這里有您想知道的互聯(lián)網營銷解決方案
前端基礎知識整理匯總(下)

接上前面兩期的內容,《前端基礎知識整理匯總(上)》、《前端基礎知識整理匯總(中)》,如果你還沒有看前面內容的話,建議你可以點開連接看看,也可以收藏著有空的時候,慢慢看。

站在用戶的角度思考問題,與客戶深入溝通,找到向陽網站設計與向陽網站推廣的解決方案,憑借多年的經驗,讓設計與互聯(lián)網技術結合,創(chuàng)造個性化、用戶體驗好的作品,建站類型包括:成都網站建設、成都網站制作、企業(yè)官網、英文網站、手機端網站、網站推廣、域名與空間、雅安服務器托管、企業(yè)郵箱。業(yè)務覆蓋向陽地區(qū)。

以下是這個內容的最后一篇內容。

react 生命周期

React v16.0前的生命周期

初始化(initialization)階段

此階段只有一個生命周期方法:constructor。

constructor()

用來做一些組件的初始化工作,如定義this.state的初始內容。如果不初始化 state 或不進行方法綁定,則不需要為 React 組件實現構造函數。為什么必須先調用super(props)?因為子類自己的this對象,必須先通過父類的構造函數完成塑造,得到與父類同樣的實例屬性和方法,然后再對其進行加工,加上子類自己的實例屬性和方法。如果不調用super方法,子類就得不到this對象。

 
 
 
 
  1. class Checkbox extends React.Component { 
  2.   constructor(props) { 
  3.     //  這時候還不能使用this 
  4.     super(props); 
  5.     //  現在開始可以使用this 
  6.     console.log(props);      //  {} 
  7.     console.log(this.props); //  {} 
  8.     this.state = {};  
  9.   } 

為什么super要傳 props?

把 props 傳進 super 是必要的,這使得基類 React.Component 可以初始化 this.props。

然而,即便在調用 super() 時沒有傳入 props 參數,你依然能夠在 render 和其它方法中訪問 this.props。

其實是 React 在調用你的構造函數之后,馬上又給實例設置了一遍 props。

 
 
 
 
  1. // React 內部 
  2. class Component { 
  3.   constructor(props) { 
  4.     this.props = props; // 初始化 this.props 
  5.     // ... 
  6.   } 
  7.  
  8. // React 內部 
  9. const instance = new Button(props); 
  10. instance.props = props; // 給實例設置 props 
  11.  
  12. // Button類組件 
  13. class Button extends React.Component { 
  14.   constructor(props) { 
  15.     super(); //  我們忘了傳入 props 
  16.     console.log(props);      //  {} 
  17.     console.log(this.props); //  undefined 
  18.   } 

掛載(Mounting)階段

此階段生命周期方法:componentWillMount => render => componentDidMount

1. componentWillMount():

在組件掛載到DOM前調用,且只會被調用一次。

每一個子組件render之前立即調用;

在此方法調用this.setState不會引起組件重新渲染,也可以把寫在這邊的內容提前到constructor()中。

2. render(): class 組件唯一必須實現的方法

當 render 被調用時,它會檢查 this.props 和 this.state 的變化并返回以下類型之一:

  • React 元素。通常通過 JSX 創(chuàng)建。例如,
  • 會被 React 渲染為 DOM 節(jié)點, 會被 React 渲染為自定義組件,無論是
  • 還是 均為 React 元素。
  • 數組或 fragments。使得 render 方法可以返回多個元素。Fragments 允許你將子列表分組,而無需向 DOM 添加額外節(jié)點。
  • Portals。可以渲染子節(jié)點到不同的 DOM 子樹中。Portal 提供了一種將子節(jié)點渲染到存在于父組件以外的 DOM 節(jié)點的優(yōu)秀的方案。
  • 字符串或數值類型。它們在 DOM 中會被渲染為文本節(jié)點。
  • 布爾類型或 null。什么都不渲染。

render() 函數應該為純函數,這意味著在不修改組件 state 的情況下,每次調用時都返回相同的結果,并且它不會直接與瀏覽器交互。不能在里面執(zhí)行this.setState,會有改變組件狀態(tài)的副作用。

3. componentDidMount

會在組件掛載后(插入 DOM 樹中)立即調用, 且只會被調用一次。依賴于 DOM 節(jié)點的初始化應該放在這里。

render之后并不會立即調用,而是所有的子組件都render完之后才會調用。

更新(update)階段

此階段生命周期方法:componentWillReceiveProps => shouldComponentUpdate => componentWillUpdate => render => componentDidUpdate。

react組件更新機制

setState引起的state更新或父組件重新render引起的props更新,更新后的state和props相對之前無論是否有變化,都將引起子組件的重新render。

1. 父組件重新render

直接重新渲染。每當父組件重新render導致的重傳props,子組件將直接跟著重新渲染,無論props是否有變化??赏ㄟ^shouldComponentUpdate方法優(yōu)化。

更新state再渲染。在componentWillReceiveProps方法中,將props轉換成自己的state,調用 this.setState() 將不會引起第二次渲染。

因為componentWillReceiveProps中判斷props是否變化了,若變化了,this.setState將引起state變化,從而引起render,此時就沒必要再做第二次因重傳props引起的render了,不然重復做一樣的渲染了。

2. 自身setState

組件本身調用setState,無論state有沒有變化??赏ㄟ^shouldComponentUpdate方法優(yōu)化。

生命周期分析

1. componentWillReceiveProps(nextProps)

此方法只調用于props引起的組件更新過程中,響應 Props 變化之后進行更新的唯一方式。

參數nextProps是父組件傳給當前組件的新props。根據nextProps和this.props來判斷重傳的props是否改變,以及做相應的處理。

2. shouldComponentUpdate(nextProps, nextState)

根據 shouldComponentUpdate() 的返回值,判斷 React 組件的輸出是否受當前 state 或 props 更改的影響。默認行為是 state 每次發(fā)生變化組件都會重新渲染。

當 props 或 state 發(fā)生變化時,shouldComponentUpdate() 會在渲染執(zhí)行之前被調用。返回值默認為 true。

首次渲染或使用 forceUpdate() 時不會調用該方法。

此方法可以將 this.props 與 nextProps 以及 this.state 與nextState 進行比較,返回true時當前組件將繼續(xù)執(zhí)行更新過程,返回false則跳過更新,以此可用來減少組件的不必要渲染,優(yōu)化組件性能。

請注意,返回 false 并不會阻止子組件在 state 更改時重新渲染。

  • 如果在componentWillReceiveProps()中執(zhí)行了this.setState,更新state,但在render前(如shouldComponentUpdate,componentWillUpdate),this.state依然指向更新前的state,不然nextState及當前組件的this.state的對比就一直是true了。
  • 應該考慮使用內置的 PureComponent 組件,而不是手動編寫 shouldComponentUpdate()。PureComponent 會對 props 和 state 進行淺層比較,并減少了跳過必要更新的可能性。

3. componentWillUpdate(nextProps, nextState)

此方法在調用render方法前執(zhí)行,在這邊可執(zhí)行一些組件更新發(fā)生前的工作,一般較少用。

4. render

render同上

5. componentDidUpdate(prevProps, prevState)

此方法在組件更新后立即調用,可以操作組件更新的DOM。

prevProps和prevState這兩個參數指的是組件更新前的props和state。

卸載階段

此階段只有一個生命周期方法:componentWillUnmount

componentWillUnmount

此方法在組件被卸載前調用,可以在這里執(zhí)行一些清理工作,比如清楚組件中使用的定時器,清楚componentDidMount中手動創(chuàng)建的DOM元素等,以避免引起內存泄漏。

componentWillUnmount() 中不應調用 setState(),因為該組件將永遠不會重新渲染。組件實例卸載后,將永遠不會再掛載它。

React v16.0 后的生命周期

React v16.0剛推出的時候,增加了一個componentDidCatch生命周期函數,這只是一個增量式修改,完全不影響原有生命周期函數;

React v16.3,引入了兩個新的生命周期:getDerivedStateFromProps,getSnapshotBeforeUpdate, 廢棄掉componentWillMount、componentWillReceiveProps 以及 componentWillUpdate 三個周期(直到React 17前還可以使用,只是會有一個警告)。

為什么要更改生命周期?

生命周期函數的更改是因為 16.3 采用了 Fiber 架構,在新的 Fiber 架構中,組件的更新分為了兩個階段:

  1. render phase:這個階段決定究竟哪些組件會被更新。
  2. commit phase:這個階段是 React 開始執(zhí)行更新(比如插入,移動,刪除節(jié)點)。

commit phase 的執(zhí)行很快,但是真實 DOM 的更新很慢,所以 React 在更新的時候會暫停再恢復組件的更新以免長時間的阻塞瀏覽器,這就意味著 render phase 可能會被執(zhí)行多次(因為有可能被打斷再重新執(zhí)行)。

  • constructor
  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate
  • getDerivedStateFromProps
  • shouldComponentUpdate
  • render

這些生命周期都屬于 render phase,render phase 可能被多次執(zhí)行,所以要避免在 render phase 中的生命周期函數中引入副作用。在 16.3 之前的生命周期很容易引入副作用,所以 16.3 之后引入新的生命周期來限制開發(fā)者引入副作用。

getDerivedStateFromProps(nextProps, prevState)

React v16.3中,static getDerivedStateFromProps只在組件創(chuàng)建和由父組件引發(fā)的更新中調用。如果不是由父組件引發(fā),那么getDerivedStateFromProps也不會被調用,如自身setState引發(fā)或者forceUpdate引發(fā)。

在React v16.4中改正了這一點,static getDerivedStateFromProps會在調用 render 方法之前調用,并且在初始掛載及后續(xù)更新時都會被調用。

特點:

  • 無副作用 。因為是處于 Fiber 的 render 階段,所以有可能會被多次執(zhí)行。所以 API 被設計為了靜態(tài)函數,無法訪問到實例的方法,也沒有 ref 來操作 DOM,這就避免了實例方法帶來副作用的可能性。但是依舊可以從 props 中獲得方法觸發(fā)副作用,所以在執(zhí)行可能觸發(fā)副作用的函數前要三思。
  • 只用來更新 state 。其這個生命周期唯一的作用就是從 nextProps 和 prevState 中衍生出一個新的 state。它應返回一個對象來更新 state,或者返回null來不更新任何內容。

getDerivedStateFromProps前面要加上static保留字,聲明為靜態(tài)方法,不然會被react忽略掉。

getDerivedStateFromProps里面的this為undefined。

static靜態(tài)方法只能Class來調用,而實例是不能調用,所以React Class組件中,靜態(tài)方法getDerivedStateFromProps無權訪問Class實例的this,即this為undefined。

getSnapshotBeforeUpdate()

getSnapshotBeforeUpdate() 只會調用一次,在最近一次渲染輸出(提交到 DOM 節(jié)點)之前調用,,所以在這個生命周期能夠獲取這一次更新前的 DOM 的信息。此生命周期的任何返回值將作為 componentDidUpdate() 的第三個參數 “snapshot” 參數傳遞, 否則componentDidUpdate的第三個參數將為 undefined。

應返回 snapshot 的值(或 null)。

錯誤處理

當渲染過程,生命周期,或子組件的構造函數中拋出錯誤時,會調用如下方法:

  • static getDerivedStateFromError():此生命周期會在后代組件拋出錯誤后被調用。它將拋出的錯誤作為參數,并返回一個值以更新 state
  • componentDidCatch():此生命周期在后代組件拋出錯誤后被調用,它應該用于記錄錯誤之類的情況。

它接收兩個參數:

  1. error —— 拋出的錯誤。
  2. info —— 帶有 componentStack key 的對象

生命周期比較

16.0 前生命周期

16.0 后生命周期:

參考:

淺析 React v16.3 新生命周期函數

react 16做了哪些更新

  1. react作為一個ui庫,將前端編程由傳統(tǒng)的命令式編程轉變?yōu)槁暶魇骄幊?,即所謂的數據驅動視圖。如果直接更新真實dom,比如將生成的html直接采用innerHtml替換,會帶來重繪重排之類的性能問題。為了盡量提高性能,React團隊引入了虛擬dom,即采用js對象來描述dom樹,通過對比前后兩次的虛擬對象,來找到最小的dom操作(vdom diff),以此提高性能。
  2. 上面提到的reactDom diff,在react 16之前,這個過程我們稱之為stack reconciler,它是一個遞歸的過程,在樹很深的時候,單次diff時間過長會造成JS線程持續(xù)被占用,用戶交互響應遲滯,頁面渲染會出現明顯的卡頓,這在現代前端是一個致命的問題。
  3. 所以為了解決這種問題,react 團隊對整個架構進行了調整,引入了fiber架構,將以前的stack reconciler替換為fiber reconciler。采用增量式渲染。引入了任務優(yōu)先級(expiration)和requestIdleCallback的循環(huán)調度算法,簡單來說就是將以前的一根筋diff更新,首先拆分成兩個階段:reconciliation與commit;第一個reconciliation階段是可打斷的,被拆分成一個個的小任務(fiber),在每一偵的渲染空閑期做小任務diff。然后是commit階段,這個階段是不拆分且不能打斷的,將diff節(jié)點的effectTag一口氣更新到頁面上。
  4. 由于reconciliation是可以被打斷的,且存在任務優(yōu)先級的問題,所以會導致commit前的一些生命周期函數多次被執(zhí)行, 如componentWillMount、componentWillReceiveProps 和 componetWillUpdate,但react官方已聲明,在React17中將會移除這三個生命周期函數。

由于每次喚起更新是從根節(jié)點(RootFiber)開始,為了更好的節(jié)點復用與性能優(yōu)化。在react中始終存workInprogressTree(future vdom) 與 oldTree(current vdom)兩個鏈表,兩個鏈表相互引用。這無形中又解決了另一個問題,當workInprogressTree生成報錯時,這時也不會導致頁面渲染崩潰,而只是更新失敗,頁面仍然還在。

React hooks原理

在React 16前,函數式組件不能擁有狀態(tài)管理?因為16以前只有類組件有對應的實例,而16以后Fiber 架構的出現,讓每一個節(jié)點都擁有對應的實例,也就擁有了保存狀態(tài)的能力。

Hooks的本質就是閉包和兩級鏈表。

  • 閉包是指有權訪問另一個函數作用域中變量或方法的函數,創(chuàng)建閉包的方式就是在一個函數內創(chuàng)建閉包函數,通過閉包函數訪問這個函數的局部變量, 利用閉包可以突破作用鏈域的特性,將函數內部的變量和方法傳遞到外部。

hooks 鏈表

一個組件包含的hooks 以鏈表的形式存儲在fiber節(jié)點的memoizedState屬性上,currentHook鏈表就是當前正在遍歷的fiber節(jié)點的。nextCurrentHook 就是即將被添加到正在遍歷fiber節(jié)點的hooks的新鏈表。

 
 
 
 
  1. let currentHook: Hook | null = null; 
  2. let nextCurrentHook: Hook | null = null; 
  3.  
  4. type Hooks = { 
  5.   memoizedState: any, // 指向當前渲染節(jié)點 Fiber 
  6.   baseState: any, // 初始化 initialState, 最新的state 
  7.   baseUpdate: Update | null, 
  8.   // 當前需要更新的 Update ,每次更新完之后,會賦值上一個 update,方便 react 在渲染錯誤的邊緣,數據回溯 
  9.   queue: UpdateQueue | null,// 可以讓state變化的,即update或dispach產生的update 
  10.   next: Hook | null, // link 到下一個 hooks 

state

其實state不是hooks獨有的,類操作的setState也存在。

memoizedState,cursor 是存在哪里的?如何和每個函數組件一一對應的?

react 會生成一棵組件樹(或Fiber 單鏈表),樹中每個節(jié)點對應了一個組件,hooks 的數據就作為組件的一個信息,存儲在這些節(jié)點上,伴隨組件一起出生,一起死亡。

為什么只能在函數最外層調用 Hook?

memoizedState 是按 hook定義的順序來放置數據的,如果 hook 順序變化,memoizedState 并不會感知到。

自定義的 Hook 是如何影響使用它的函數組件的?

共享同一個 memoizedState,共享同一個順序。

“Capture Value” 特性是如何產生的?

每一次 ReRender 的時候,都是重新去執(zhí)行函數組件了,對于之前已經執(zhí)行過的函數組件,并不會做任何操作。

react setState 異步更新

setState 實現原理

setState 通過一個隊列機制來實現 state 更新,當執(zhí)行 setState() 時,會將需要更新的 state 淺合并后放入 狀態(tài)隊列,而不會立即更新 state,隊列機制可以高效的批量更新 state。如果不通過setState,直接修改this.state 的值,則不會放入狀態(tài)隊列,當下一次調用 setState 對狀態(tài)隊列進行合并時,之前對 this.state 的修改將會被忽略,造成無法預知的錯誤。

setState()有的同步有的異步?

在React中, 如果是由React引發(fā)的事件處理(比如通過onClick引發(fā)的事件處理),調用setState不會同步更新this.state,除此之外的setState調用會同步執(zhí)行this.state 。

所謂“除此之外”,指的是繞過React通過addEventListener直接添加的事件處理函數,還有通過setTimeout/setInterval產生的異步調用。

原因: 在React的setState函數實現中,會根據一個變量isBatchingUpdates判斷是直接更新this.state還是放到隊列中回頭再說,而isBatchingUpdates默認是false,也就表示setState會同步更新this.state,但是,有一個函數batchedUpdates,這個函數會把isBatchingUpdates修改為true,而當React在調用事件處理函數之前就會調用這個batchedUpdates,造成的后果,就是由React控制的事件處理過程setState不會同步更新this.state。

  • setState的“異步”并不是說內部由異步代碼實現,其實本身執(zhí)行的過程和代碼都是同步的,只是合成事件和鉤子函數的調用順序在更新之前,導致在合成事件和鉤子函數中沒法立馬拿到更新后的值,形式了所謂的“異步”,可以通過第二個參數 setState(partialState, callback) 中的callback拿到更新后的結果。

調用風險

當調用 setState 時,實際上是會執(zhí)行 enqueueSetState 方法,并會對 partialState 及 _pendingStateQueue 隊列進行合并操作,最終通過 enqueueUpdate 執(zhí)行 state 更新。

而 performUpdateIfNecessary 獲取 _pendingElement、 _pendingStateQueue、_pendingForceUpdate,并調用 reaciveComponent 和 updateComponent 來進行組件更新。

但,如果在 shouldComponentUpdate 或 componentWillUpdate 方法里調用 this.setState 方法,就會造成崩潰。

這是因為在 shouldComponentUpdate 或 componentWillUpdate 方法里調用 this.setState 時,this._pendingStateQueue!=null,則 performUpdateIfNecessary 方法就會調用 updateComponent 方法進行組件更新,而 updateComponent 方法又會調用 shouldComponentUpdate和componentWillUpdate 方法,因此造成循環(huán)調用,使得瀏覽器內存占滿后崩潰。

React Fiber

掉幀:在頁面元素很多,且需要頻繁刷新的場景下,React 15 會出現掉幀的現象,其根本原因,是大量的同步計算任務阻塞了瀏覽器的 UI 渲染。

默認情況下,JS 運算、頁面布局和頁面繪制都是運行在瀏覽器的主線程當中,他們之間是互斥的關系。

如果 JS 運算持續(xù)占用主線程,頁面就沒法得到及時的更新。

當我們調用setState更新頁面的時候,React 會遍歷應用的所有節(jié)點,計算出差異,然后再更新 UI,整個過程不能被打斷。

如果頁面元素很多,整個過程占用的時機就可能超過 16 毫秒,就容易出現掉幀的現象。

如何解決主線程長時間被 JS 運算?將JS運算切割為多個步驟,分批完成。在完成一部分任務之后,將控制權交回給瀏覽器,讓瀏覽器有時間進行頁面的渲染。等瀏覽器忙完之后,再繼續(xù)之前未完成的任務。

React 15 及以下版本通過遞歸的方式進行渲染,使用的是 JS 引擎自身的函數調用棧,它會一直執(zhí)行到??諡橹?。

而Fiber實現了自己的組件調用棧,它以鏈表的形式遍歷組件樹,可以靈活的暫停、繼續(xù)和丟棄執(zhí)行的任務。實現方式是使用了瀏覽器的requestIdleCallback

  • window.requestIdleCallback()會在瀏覽器空閑時期依次調用函數,這就可以讓開發(fā)者在主事件循環(huán)中執(zhí)行后臺或低優(yōu)先級的任務,而且不會對像動畫和用戶交互這些延遲觸發(fā)但關鍵的事件產生影響。函數一般會按先進先調用的順序執(zhí)行,除非函數在瀏覽器調用它之前就到了它的超時時間。

React 框架內部的運作可以分為 3 層:

  • Virtual DOM 層,描述頁面長什么樣。
  • Reconciler 層,負責調用組件生命周期方法,進行 Diff 運算等。
  • Renderer 層,根據不同的平臺,渲染出相應的頁面,比較常見的是 ReactDOM 和 ReactNative。

Fiber 表征reconciliation階段所能拆分的最小工作單元,其實指的是一種鏈表樹,它可以用一個純 JS 對象來表示:

 
 
 
 
  1. const fiber = { 
  2.   stateNode: {},    // 節(jié)點實例 
  3.   child: {},        // 子節(jié)點 
  4.   sibling: {},      // 兄弟節(jié)點 
  5.   return: {},       // 表示處理完成后返回結果所要合并的目標,通常指向父節(jié)點 
  6. }; 

Reconciler區(qū)別

  • 以前的 Reconciler 被命名為Stack Reconciler。Stack Reconciler 運作的過程是不能被打斷的,必須一條道走到黑;
  • Fiber Reconciler 每執(zhí)行一段時間,都會將控制權交回給瀏覽器,可以分段執(zhí)行;

從Stack Reconciler到Fiber Reconciler,源碼層面其實就是干了一件遞歸改循環(huán)的事情。

scheduling(調度)

scheduling(調度)是fiber reconciliation的一個過程,主要是進行任務分配,達到分段執(zhí)行。任務的優(yōu)先級有六種:

  • synchronous,與之前的Stack Reconciler操作一樣,同步執(zhí)行
  • task,在next tick之前執(zhí)行
  • animation,下一幀之前執(zhí)行
  • high,在不久的將來立即執(zhí)行
  • low,稍微延遲執(zhí)行也沒關系
  • offscreen,下一次render時或scroll時才執(zhí)行

優(yōu)先級高的任務(如鍵盤輸入)可以打斷優(yōu)先級低的任務(如Diff)的執(zhí)行,從而更快的生效。

Fiber Reconciler 在執(zhí)行過程中,會分為 2 個階段:

  • 階段一,生成 Fiber 樹,得出需要更新的節(jié)點信息。這一步是一個漸進的過程,可以被打斷。
  • 階段二,將需要更新的節(jié)點一次過批量更新,這個過程不能被打斷。

階段一可被打斷的特性,讓優(yōu)先級更高的任務先執(zhí)行,從框架層面大大降低了頁面掉幀的概率。

參考:

React Fiber 原理介紹

React Fiber

HOC 與render props區(qū)別

Render Props: 把將要包裹的組件作為props屬性傳入,然后容器組件調用這個屬性,并向其傳參。

實現方式:

1.通過props.children(props),props.children返回的是UI元素。 JSX 標簽中的所有內容都會作為一個 children prop 傳遞給 RenderProps組件。因為 RenderProps 將 {props.children} 渲染在一個

中,被傳遞的這些子組件最終都會出現在輸出結果中。

 
 
 
 
  1. // 定義 
  2. const RenderProps = props => 
     
  3.    {props.children(props)} 
 
  •  
  • // 調用 
  •  
  •     {() => <>Hello RenderProps} 
  •  
  •  2.通過props中的任何函數, 自行定義傳入內容

     
     
     
     
    1. // 定義 
    2. const LoginForm = props => { 
    3.   const flag = false; 
    4.   const allProps = { flag, ...props }; 
    5.  
    6.   if (flag) { 
    7.     return <>{props.login(allProps)} 
    8.   } else { 
    9.     return <>{props.notLogin(allProps)} 
    10.   } 
    11.  
    12. // 調用 
    13.   login={() => 

      LOGIN

    14.   noLogin={() => 

      NOT LOGIN

    15. /> 

     優(yōu)點:

    1、支持ES6

    2、不用擔心props命名問題,在render函數中只取需要的state

    3、不會產生無用的組件加深層級

    4、render props模式的構建都是動態(tài)的,所有的改變都在render中觸發(fā),可以更好的利用組件內的生命周期。

    HOC: 接受一個組件作為參數,返回一個新的組件的函數。

     
     
     
     
    1. class Home extends React.Component { 
    2.   // UI 
    3.  
    4. export default Connect()(Home); 

    高階組件由于每次都會返回一個新的組件,對于react來說,這是不利于diff和狀態(tài)復用的,所以高階組件的包裝不能在render 方法中進行,而只能像上面那樣在組件聲明時包裹,這樣也就不利于動態(tài)傳參。

    優(yōu)點:

    1、支持ES6

    2、復用性強,HOC為純函數且返回值為組件,可以多層嵌套

    3、支持傳入多個參數,增強了適用范圍

    缺點:

    1、當多個HOC一起使用時,無法直接判斷子組件的props是哪個HOC負責傳遞的

    2、多個組件嵌套,容易產生同樣名稱的props

    3、HOC可能會產生許多無用的組件,加深了組件的層級

    總的來說,render props其實和高階組件類似,就是在puru component上增加state,響應react的生命周期。

    React 通信

    react的數據流是單向的,最常見的就是通過props由父組件向子組件傳值。

    • 父向子通信:傳入props
    • 子向父通信:父組件向子組件傳一個函數,然后通過這個函數的回調,拿到子組件傳過來的值
    • 父向孫通信:利用context傳值。React.createContext()
    • 兄弟間通信:

    1、找一個相同的父組件,既可以用props傳遞數據,也可以用context的方式來傳遞數據。

    2、用一些全局機制去實現通信,比如redux等

    3、發(fā)布訂閱模式

    react合成事件

    React 合成事件(SyntheticEvent)是 React 模擬原生 DOM 事件所有能力的一個事件對象,即瀏覽器原生事件的跨瀏覽器包裝器。

    為什么要使用合成事件?

    1.進行瀏覽器兼容,實現更好的跨平臺

    React 采用的是頂層事件代理機制,能夠保證冒泡一致性,可以跨瀏覽器執(zhí)行。React 提供的合成事件用來抹平不同瀏覽器事件對象之間的差異,將不同平臺事件模擬合成事件。

    2.避免垃圾回收

    事件對象可能會被頻繁創(chuàng)建和回收,因此 React 引入事件池,在事件池中獲取或釋放事件對象。即 React 事件對象不會被釋放掉,而是存放進一個數組中,當事件觸發(fā),就從這個數組中彈出,避免頻繁地去創(chuàng)建和銷毀(垃圾回收)。

    3.方便事件統(tǒng)一管理和事務機制

    實現原理

    在 React 中,“合成事件”會以事件委托方式綁定在 document 對象上,并在組件卸載(unmount)階段自動銷毀綁定的事件。

    合成事件和原生事件

    當真實 DOM 元素觸發(fā)事件,會冒泡到 document 對象后,再處理 React 事件;所以會先執(zhí)行原生事件,然后處理 React 事件;最后真正執(zhí)行 document 上掛載的事件。

    合成事件和原生事件最好不要混用。原生事件中如果執(zhí)行了stopPropagation方法,則會導致其他React事件失效。因為所有元素的事件將無法冒泡到document上,所有的 React 事件都將無法被注冊。

    合成事件的事件池

    合成事件對象池,是 React 事件系統(tǒng)提供的一種性能優(yōu)化方式。合成事件對象在事件池統(tǒng)一管理,不同類型的合成事件具有不同的事件池。

    react 虛擬dom

    什么是虛擬dom?

    在 React 中,render 執(zhí)行的結果得到的并不是真正的 DOM 節(jié)點,而是輕量級的 JavaScript 對象,我們稱之為 virtual DOM。它通過JS的Object對象模擬DOM中的節(jié)點,然后再通過特定的render方法將其渲染成真實的DOM節(jié)點。

    虛擬 DOM 是 React 的一大亮點,具有batching(批處理) 和高效的 Diff 算法。batching 把所有的 DOM 操作搜集起來,一次性提交給真實的 DOM。diff 算法時間復雜度也從標準的的 Diff 算法的 O(n^3) 降到了 O(n)。

    batching(批處理)

    主要思想是,無論setState您在React事件處理程序或同步生命周期方法中進行多少次調用,它都將被批處理成一個更新, 最終只有一次重新渲染。

    虛擬 DOM 與 原生 DOM

    如果沒有 Virtual DOM,就需要直接操作原生 DOM。在一個大型列表所有數據都變了的情況下,直接重置 innerHTML還算合理,但是,只有一行數據發(fā)生變化時,它也需要重置整個 innerHTML,這就造成了大量浪費。

    innerHTML 和 Virtual DOM 的重繪性能消耗:

    innerHTML: render html string + 重新創(chuàng)建所有 DOM 元素

    Virtual DOM: render Virtual DOM + diff + 必要的 DOM 更新

    Virtual DOM render + diff 顯然比渲染 html 字符串要慢,但是它依然是純 js 層面的計算,比起后面的 DOM 操作來說,依然便宜了太多。innerHTML 的總計算量不管是 js 計算還是 DOM 操作都是和整個界面的大小相關,但 Virtual DOM 的計算量只有 js 計算和界面大小相關,DOM 操作是和數據的變動量相關。

    虛擬 DOM 與 MVVM

    相比起 React,其他 MVVM 系框架比如 Angular, Knockout , Vue ,Avalon 采用的都是數據綁定。通過 Directive/Binding 對象,觀察數據變化并保留對實際 DOM 元素的引用,當有數據變化時進行對應的操作。MVVM 的變化檢查是數據層面的,而 React 的檢查是 DOM 結構層面的。

    MVVM 的性能也根據變動檢測的實現原理有所不同:Angular 依賴于臟檢查;Knockout/Vue/Avalon 采用了依賴收集。

    • 臟檢查:scope digest(watcher count) ) + 必要 DOM 更新
    • 依賴收集:重新收集依賴(data change) ) + 必要 DOM 更新

    Angular 最不效率的地方在于任何小變動都有的和 watcher 數量相關的性能代價,當所有數據都變了的時候,Angular更有效。依賴收集在初始化和數據變化的時候都需要重新收集依賴,這個代價在小量更新的時候幾乎可以忽略,但在數據量龐大的時候也會產生一定的消耗。

    性能比較

    在比較性能的時候,要分清楚初始渲染、小量數據更新、大量數據更新這些不同的場合。Virtual DOM、臟檢查 MVVM、數據收集 MVVM 在不同場合各有不同的表現和不同的優(yōu)化需求。

    Virtual DOM 為了提升小量數據更新時的性能,也需要針對性的優(yōu)化,比如 shouldComponentUpdate 或是 immutable data。

    • 初始渲染:Virtual DOM > 臟檢查 >= 依賴收集
    • 小量數據更新:依賴收集 >> Virtual DOM + 優(yōu)化 > 臟檢查(無法優(yōu)化)> Virtual DOM 無優(yōu)化
    • 大量數據更新:臟檢查 + 優(yōu)化 >= 依賴收集 + 優(yōu)化 > Virtual DOM(無法/無需優(yōu)化)>> MVVM 無優(yōu)化

    diff 算法

    傳統(tǒng) diff 算法通過循環(huán)遞歸對節(jié)點進行依次對比,算法復雜度達到 O(n^3),其中 n 是樹中節(jié)點的總數。O(n^3) 意味著如果要展示1000個節(jié)點,就要依次執(zhí)行上十億次的比較, 這是無法滿足現代前端性能要求的。

    diff 算法主要包括幾個步驟:

    • 用 JS 對象的方式來表示 DOM 樹的結構,然后根據這個對象構建出真實的 DOM 樹,插到文檔中。
    • 當狀態(tài)變更的時候,重新構造一棵新的對象樹。然后用新的樹和舊的樹進行比較,記錄兩棵樹的差異, 最后把所記錄的差異應用到所構建的真正的DOM樹上,視圖更新。

    diff 策略

    React 通過制定大膽的diff策略,將diff算法復雜度從 O(n^3) 轉換成 O(n) 。

    • React 通過分層求異的策略,對 tree diff 進行算法優(yōu)化;
    • React 通過相同類生成相似樹形結構,不同類生成不同樹形結構的策略,對 component diff 進行算法優(yōu)化;
    • React 通過設置唯一 key的策略,對 element diff 進行算法優(yōu)化;

    tree diff(層級比較)

    React 對樹進行分層比較,兩棵樹只會對同一層次的節(jié)點進行比較。

    當發(fā)現節(jié)點已經不存在,則該節(jié)點及其子節(jié)點會被完全刪除掉,不會進行進一步的比較。

    這樣只需要對樹進行一次遍歷,便能完成整個 DOM 樹的比較。

    當出現節(jié)點跨層級移動時,并不會出現移動操作,而是以該節(jié)點為根節(jié)點的樹被重新創(chuàng)建,這是一種影響 React 性能的操作,因此 React 官方建議不要進行 DOM 節(jié)點跨層級的操作。

    • 先進行樹結構的層級比較,對同一個父節(jié)點下的所有子節(jié)點進行比較;
    • 接著看節(jié)點是什么類型的,是組件就做 Component Diff;
    • 如果節(jié)點是標簽或者元素,就做 Element Diff;

    注意:在開發(fā)組件時,保持穩(wěn)定的 DOM 結構會有助于性能的提升。例如,可以通過 CSS 隱藏或顯示節(jié)點,而不是真的移除或添加 DOM 節(jié)點。

    component diff(組件比較)

    • 如果是同一類型的組件,按照原策略繼續(xù)比較 virtual DOM tree。
    • 如果不是,則將該組件判斷為 dirty component,替換整個組件下的所有子節(jié)點。舉個例子,當一個元素從 變成

    對于同一類型的組件,有可能其 Virtual DOM 沒有任何變化,如果能夠確切的知道這點那可以節(jié)省大量的 diff 運算時間。因此 React 允許用戶通過 shouldComponentUpdate() 來判斷該組件是否需要進行 diff。

    對于兩個不同類型但結構相似的組件,不會比較二者的結構,而且替換整個組件的所有內容。不同類型的 component 是很少存在相似 DOM tree 的機會,因此這種極端因素很難在實現開發(fā)過程中造成重大影響的。

    element diff (元素比較)

    當節(jié)點處于同一層級時,React diff 提供了三種節(jié)點操作,分別為:INSERT_MARKUP(插入)、MOVE_EXISTING(移動)和 REMOVE_NODE(刪除)。

    • INSERT_MARKUP,新的 component 類型不在老集合里, 即是全新的節(jié)點,需要對新節(jié)點執(zhí)行插入操作。
    • MOVE_EXISTING,在老集合有新 component 類型,且 element 是可更新的類型,這種情況下需要做移動操作,可以復用以前的 DOM 節(jié)點。
    • REMOVE_NODE,老 component 類型,在新集合里也有,但對應的 element 不同則不能直接復用和更新,需要執(zhí)行刪除操作,或者老 component 不在新集合里的,也需要執(zhí)行刪除操作。
     
     
     
     
      •  
      •   
      • Duke
      •  
      •   
      • Villanova
      •  
       
    1.  
      •  
      •   
      • Connecticut
      •  
      •   
      • Duke
      •  
      •   
      • Villanova
      •  
       

     React 并不會意識到應該保留

  • Duke
  • Villanova
  • ,而是會重建每一個子元素,不會進行移動 DOM 操作。

    key 優(yōu)化

    為了解決上述問題,React 引入了 key 屬性, 對同一層級的同組子節(jié)點,添加唯一 key 進行區(qū)分。

    當子元素擁有 key 時,React 使用 key 來匹配原有樹上的子元素以及最新樹上的子元素。如果有相同的節(jié)點,無需進行節(jié)點刪除和創(chuàng)建,只需要將老集合中節(jié)點的位置進行移動,更新為新集合中節(jié)點的位置。

    • 在開發(fā)過程中,盡量減少類似將最后一個節(jié)點移動到列表首部的操作,當節(jié)點數量過大或更新操作過于頻繁時,在一定程度上會影響 React 的渲染性能。
    • key 不需要全局唯一,但在列表中需要保持唯一。
    • Key 應該具有穩(wěn)定,可預測,以及列表內唯一的特質。不穩(wěn)定的 key(比如通過 Math.random() 生成的)會導致許多組件實例和 DOM 節(jié)點被不必要地重新創(chuàng)建,這可能導致性能下降和子組件中的狀態(tài)丟失。

    react與vue區(qū)別

    1. 監(jiān)聽數據變化的實現原理不同

    Vue通過 getter/setter以及一些函數的劫持,能精確知道數據變化。

    React默認是通過比較引用的方式(diff)進行的,如果不優(yōu)化可能導致大量不必要的VDOM的重新渲染。

    2. 數據流不同

    Vue1.0中可以實現兩種雙向綁定:父子組件之間props可以雙向綁定;組件與DOM之間可以通過v-model雙向綁定。

    Vue2.x中父子組件之間不能雙向綁定了(但是提供了一個語法糖自動幫你通過事件的方式修改)。

    React一直不支持雙向綁定,提倡的是單向數據流,稱之為onChange/setState()模式。

    3. HoC和mixins

    Vue組合不同功能的方式是通過mixin,Vue中組件是一個被包裝的函數,并不簡單的就是我們定義組件的時候傳入的對象或者函數。

    React組合不同功能的方式是通過HoC(高階組件)。

    4. 模板渲染方式的不同

    模板的語法不同,React是通過JSX渲染模板, Vue是通過一種拓展的HTML語法進行渲染。

    模板的原理不同,React通過原生JS實現模板中的常見語法,比如插值,條件,循環(huán)等。而Vue是在和組件JS代碼分離的單獨的模板中,通過指令來實現的,比如 v-if 。

    舉個例子,說明React的好處:react中render函數是支持閉包特性的,所以我們import的組件在render中可以直接調用。但是在Vue中,由于模板中使用的數據都必須掛在 this 上進行一次中轉,所以我們import 一個組件完了之后,還需要在 components 中再聲明下。

    5. 渲染過程不同

    Vue可以更快地計算出Virtual DOM的差異,這是由于它會跟蹤每一個組件的依賴關系,不需要重新渲染整個組件樹。

    React當狀態(tài)被改變時,全部子組件都會重新渲染。通過shouldComponentUpdate這個生命周期方法可以進行控制,但Vue將此視為默認的優(yōu)化。

    6. 框架本質不同

    Vue本質是MVVM框架,由MVC發(fā)展而來;

    React是前端組件化框架,由后端組件化發(fā)展而來。

    性能優(yōu)化

    1. 靜態(tài)資源使用 CDN

    CDN是一組分布在多個不同地理位置的 Web 服務器。當服務器離用戶越遠時,延遲越高。

    2. 無阻塞

    頭部內聯(lián)的樣式和腳本會阻塞頁面的渲染,樣式放在頭部并使用link方式引入,腳本放在尾部并使用異步方式加載

    3. 壓縮文件

    壓縮文件可以減少文件下載時間。

    1.在 webpack 可以使用如下插件進行壓縮:

    JavaScript:UglifyPlugin

    CSS :MiniCssExtractPlugin

    HTML:HtmlWebpackPlugin

    2.使用 gzip 壓縮。通過向 HTTP 請求頭中的 Accept-Encoding 頭添加 gzip 標識來開啟這一功能。

    4. 圖片優(yōu)化

    1. 圖片懶加載
    2. 響應式圖片:瀏覽器根據屏幕大小自動加載合適的圖片。
    3. 降低圖片質量:方法有兩種,一是通過 webpack 插件 image-webpack-loader,二是通過在線網站進行壓縮。

    5. 減少重繪重排

    • 降低 CSS 選擇器的復雜性
    • 使用 transform 和 opacity 屬性更改來實現動畫
    • 用 JavaScript 修改樣式時,最好不要直接寫樣式,而是替換 class 來改變樣式。
    • 如果要對 DOM 元素執(zhí)行一系列操作,可以將 DOM 元素脫離文檔流,修改完成后,再將它帶回文檔。推薦使用隱藏元素(display:none)或文檔碎片(DocumentFragement),都能很好的實現這個方案。

    6. 使用 requestAnimationFrame 來實現視覺變化

    window.requestAnimationFrame() 告訴瀏覽器——你希望執(zhí)行一個動畫,并且要求瀏覽器在下次重繪之前調用指定的回調函數更新動畫。該方法需要傳入一個回調函數作為參數,該回調函數會在瀏覽器下一次重繪之前執(zhí)行

    7. webpack 打包, 添加文件緩存

    index.html 設置成 no-cache,這樣每次請求的時候都會比對一下 index.html 文件有沒變化,如果沒變化就使用緩存,有變化就使用新的 index.html 文件。

    其他所有文件一律使用長緩存,例如設置成緩存一年 maxAge: 1000 * 60 * 60 * 24 * 365。

    前端代碼使用 webpack 打包,根據文件內容生成對應的文件名,每次重新打包時只有內容發(fā)生了變化,文件名才會發(fā)生變化。

    max-age: 設置緩存存儲的最大周期,超過這個時間緩存被認為過期(單位秒)。在這個時間前,瀏覽器讀取文件不會發(fā)出新請求,而是直接使用緩存。

    指定 no-cache 表示客戶端可以緩存資源,每次使用緩存資源前都必須重新驗證其有效性

    輸入url后發(fā)生了什么

    1. DNS域名解析;
    2. 建立TCP連接(三次握手);
    3. 發(fā)送HTTP請求;
    4. 服務器處理請求;
    5. 返回響應結果;
    6. 關閉TCP連接(四次握手);
    7. 瀏覽器解析HTML;
    8. 瀏覽器布局渲染;

    1. DNS域名解析:拿到服務器ip

    客戶端收到你輸入的域名地址后,它首先去找本地的hosts文件,檢查在該文件中是否有相應的域名、IP對應關系,如果有,則向其IP地址發(fā)送請求,如果沒有,再去找DNS服務器。

    2. 建立TCP鏈接:客戶端鏈接服務器

    TCP提供了一種可靠、面向連接、字節(jié)流、傳輸層的服務。對于客戶端與服務器的TCP鏈接,必然要說的就是『三次握手』。“3次握手”的作用就是雙方都能明確自己和對方的收、發(fā)能力是正常的。

    客戶端發(fā)送一個帶有SYN標志的數據包給服務端,服務端收到后,回傳一個帶有SYN/ACK標志的數據包以示傳達確認信息,最后客戶端再回傳一個帶ACK標志的數據包,代表握手結束,連接成功。

    SYN —— 用于初如化一個連接的序列號。

    ACK —— 確認,使得確認號有效。

    RST —— 重置連接。

    FIN —— 該報文段的發(fā)送方已經結束向對方發(fā)送數據。

    • 客戶端:“你好,在家不?!?-- SYN
    • 服務端:“在的,你來吧?!?-- SYN + ACK
    • 客戶端:“好嘞?!?-- ACK

    3. 發(fā)送HTTP請求

    4. 服務器處理請求

    5. 返回響應結果

    6. 關閉TCP連接(需要4次握手)

    為了避免服務器與客戶端雙方的資源占用和損耗,當雙方沒有請求或響應傳遞時,任意一方都可以發(fā)起關閉請求。

    關閉連接時,服務器收到對方的FIN報文時,僅僅表示客戶端不再發(fā)送數據了但是還能接收數據,而服務器也未必全部數據都發(fā)送給客戶端,所以服務器可以立即關閉,也可以發(fā)送一些數據給對方后,再發(fā)送FIN報文給對方來表示同意現在關閉連接,因此,己方ACK和FIN一般都會分開發(fā)送,從而導致多了一次。

    • 客戶端:“兄弟,我這邊沒數據要傳了,咱關閉連接吧?!?-- FIN + seq
    • 服務端:“收到,我看看我這邊有木有數據了?!?-- ACK + seq + ack
    • 服務端:“兄弟,我這邊也沒數據要傳你了,咱可以關閉連接了?!?- FIN + ACK + seq + ack
    • 客戶端:“好嘞?!?-- ACK + seq + ack

    7. 瀏覽器解析HTML

    瀏覽器需要加載解析的不僅僅是HTML,還包括CSS、JS,以及還要加載圖片、視頻等其他媒體資源。

    瀏覽器通過解析HTML,生成DOM樹,解析CSS,生成CSSOM樹,然后通過DOM樹和CSSPOM樹生成渲染樹。渲染樹與DOM樹不同,渲染樹中并沒有head、display為none等不必顯示的節(jié)點。

    • 瀏覽器的解析過程并非是串連進行的,比如在解析CSS的同時,可以繼續(xù)加載解析HTML,但在解析執(zhí)行JS腳本時,會停止解析后續(xù)HTML,會出現阻塞問題。

    8. 瀏覽器渲染頁面

    根據渲染樹布局,計算CSS樣式,即每個節(jié)點在頁面中的大小和位置等幾何信息。HTML默認是流式布局的,CSS和js會打破這種布局,改變DOM的外觀樣式以及大小和位置。最后瀏覽器繪制各個節(jié)點,將頁面展示給用戶。

    replaint:屏幕的一部分重畫,不影響整體布局,比如某個CSS的背景色變了,但元素的幾何尺寸和位置不變。

    reflow:意味著元素的幾何尺寸變了,需要重新計算渲染樹。

    參考:

    細說瀏覽器輸入URL后發(fā)生了什么

    瀏覽器輸入 URL 后發(fā)生了什么?

    前端路由

    什么是路由

    路由是用來跟后端服務器進行交互的一種方式,通過不同的路徑請求不同的資源。

    路由這概念最開始是在后端出現, 在前后端不分離的時期, 由后端來控制路由, 服務器接收客戶端的請求,解析對應的url路徑, 并返回對應的頁面/資源。

    前端路由

    Ajax,全稱 Asynchronous JavaScript And XML,是瀏覽器用來實現異步加載的一種技術方案。

    在Ajax沒有出現時期,大多數的網頁都是通過直接返回 HTML,用戶的每次更新操作都需要重新刷新頁面,及其影響交互體驗。為了解決這個問題,提出了Ajax(異步加載方案), 有了 Ajax 后,用戶交互就不用每次都刷新頁面。后來出現SPA單頁應用。

    SPA 中用戶的交互是通過 JS 改變 HTML 內容來實現的,頁面本身的 url 并沒有變化,這導致了兩個問題:

    • SPA 無法記住用戶的操作記錄,無論是刷新、前進還是后退,都無法展示用戶真實的期望內容。
    • SPA 中雖然由于業(yè)務的不同會有多種頁面展示形式,但只有一個 url,對 SEO 不友好,不方便搜索引擎進行收錄。

    前端路由就是為了解決上述問題而出現的。

    前端路由的實現方式

    前端路由的實現實際上是檢測 url 的變化,截獲 url 地址,解析來匹配路由規(guī)則。有下面兩種實現方式:

    1. Hash模式

    hash 就是指 url 后的 # 號以及后面的字符。 #后面 hash 值的變化,并不會導致瀏覽器向服務器發(fā)出請求,瀏覽器不發(fā)請求,也就不會刷新頁面。

    hash 的改變會觸發(fā) hashchange 事件,可以用onhashchange事件來監(jiān)聽hash值的改變。

     
     
     
     
    1. // 監(jiān)聽hash變化,點擊瀏覽器的前進后退會觸發(fā) 
    2. window.onhashchange = function() { ... } 
    3.  
    4. window.addEventListener('hashchange', function(event) { ...}, false); 

    2.History 模式

    在 HTML5 之前,瀏覽器就已經有了 history 對象。但在早期的 history 中只能用于多頁面的跳轉:

     
     
     
     
    1. history.go(-1);       // 后退一頁 
    2. history.go(2);        // 前進兩頁 
    3. history.forward();    // 前進一頁 
    4. history.back();       // 后退一頁 

    在 HTML5 的規(guī)范中,history 新增了幾個 API:

     
     
     
     
    1. history.pushState();   // 向當前瀏覽器會話的歷史堆棧中添加一個狀態(tài) 
    2. history.replaceState();// 修改了當前的歷史記錄項(不是新建一個) 
    3. history.state          // 返回一個表示歷史堆棧頂部的狀態(tài)的值 

    由于 history.pushState() 和 history.replaceState() 可以改變 url 同時,不會刷新頁面,所以在 HTML5 中的 histroy 具備了實現前端路由的能力。

    window對象提供了onpopstate事件來監(jiān)聽歷史棧的改變,一旦歷史棧信息發(fā)生改變, 便會觸發(fā)該事件。

    • 調用history.pushState()或history.replaceState()不會觸發(fā)popstate事件。只有在做出瀏覽器動作時,才會觸發(fā)該事件,例如執(zhí)行history.back()或history.forward()后觸發(fā) window.onpopstate事件。
     
     
     
     
    1. // 歷史棧改變 
    2. window.onpopstate = function() { ... } 

    注意:pushState() 不會造成 hashchange 事件調用, 即使新的URL和之前的URL只是錨的數據不同。

    兩種模式對比

    前端路由實踐

    vue-router/react-router 都是基于前端路由的原理實現的~

    react-router常用的 history 有三種形式:

    • browserHistory: 使用瀏覽器中的History API 用于處理 URL。history 在 DOM 上的實現,用于支持 HTML5 history API 的瀏覽器。
    • hashHistory: 使用 URL 中的 hash(#)部分去創(chuàng)建路由。history 在 DOM 上的實現,用于舊版瀏覽器。
    • createMemoryHistory: 不會在地址欄被操作或讀取,history 在內存上的實現,用于測試或非 DOM 環(huán)境(例如 React Native)。

    Babel Plugin與preset區(qū)別

    Babel是代碼轉換器,比如將ES6轉成ES5,或者將JSX轉成JS等。借助Babel,開發(fā)者可以提前用上新的JS特性。

    • 原始代碼 --> [Babel Plugin] --> 轉換后的代碼

    Plugin

    實現Babel代碼轉換功能的核心,就是Babel插件(plugin)。Babel插件一般盡可能拆成小的力度,開發(fā)者可以按需引進, 既提高了性能,也提高了擴展性。比如對ES6轉ES5的功能,Babel官方拆成了20+個插件。開發(fā)者想要體驗ES6的箭頭函數特性,那只需要引入transform-es2015-arrow-functions插件就可以,而不是加載ES6全家桶。

    Preset

    可以簡單的把Babel Preset視為Babel Plugin的集合。想要將所有ES6的代碼轉成ES5,逐個插件引入的效率比較低下, 就可以采用Babel Preset。比如babel-preset-es2015就包含了所有跟ES6轉換有關的插件。

    Plugin與Preset執(zhí)行順序

    可以同時使用多個Plugin和Preset,此時,它們的執(zhí)行順序非常重要。

    1. 先執(zhí)行完所有Plugin,再執(zhí)行Preset。
    2. 多個Plugin,按照聲明次序順序執(zhí)行。
    3. 多個Preset,按照聲明次序逆序執(zhí)行。

    比如.babelrc配置如下,那么執(zhí)行的順序為:

    1. Plugin:transform-react-jsx、transform-async-to-generator
    2. Preset:es2016、es2015
     
     
     
     
    1.   "presets": [  
    2.     "es2015", 
    3.     "es2016"     
    4.   ], 
    5.   "plugins": [  
    6.     "transform-react-jsx", 
    7.     "transform-async-to-generator" 
    8.   ] 

    怎樣開發(fā)和部署前端代碼

    為了進一步提升網站性能,會把靜態(tài)資源和動態(tài)網頁分集群部署,靜態(tài)資源會被部署到CDN節(jié)點上,網頁中引用的資源也會變成對應的部署路徑。當需要更新靜態(tài)資源的時候,同時也會更新html中的引用。

    如果同時改了頁面結構和樣式,也更新了靜態(tài)資源對應的url地址,現在要發(fā)布代碼上線,是先上線頁面,還是先上線靜態(tài)資源?

    1. 先部署頁面,再部署資源:在二者部署的時間間隔內,如果有用戶訪問頁面,就會在新的頁面結構中加載舊的資源,并且把這個舊版本的資源當做新版本緩存起來,其結果就是:用戶訪問到了一個樣式錯亂的頁面,除非手動刷新,否則在資源緩存過期之前,頁面會一直執(zhí)行錯誤。
    2. 先部署資源,再部署頁面:在部署時間間隔之內,有舊版本資源本地緩存的用戶訪問網站,由于請求的頁面是舊版本的,資源引用沒有改變,瀏覽器將直接使用本地緩存,這種情況下頁面展現正常;但沒有本地緩存或者緩存過期的用戶訪問網站,就會出現舊版本頁面加載新版本資源的情況,導致頁面執(zhí)行錯誤,但當頁面完成部署,這部分用戶再次訪問頁面又會恢復正常了。

    這個奇葩問題,起源于資源的 覆蓋式發(fā)布,用 待發(fā)布資源 覆蓋 已發(fā)布資源,就有這種問題。

    解決它也好辦,就是實現 非覆蓋式發(fā)布。用文件的摘要信息來對資源文件進行重命名,把摘要信息放到資源文件發(fā)布路徑中,這樣,內容有修改的資源就變成了一個新的文件發(fā)布到線上,不會覆蓋已有的資源文件。

    上線過程中,先全量部署靜態(tài)資源,再灰度部署頁面,整個問題就比較完美的解決了。

    大公司的靜態(tài)資源優(yōu)化方案,基本上要實現這么幾個東西:

    1. 配置超長時間的本地緩存 —— 節(jié)省帶寬,提高性能
    2. 采用內容摘要作為緩存更新依據 —— 精確的緩存控制
    3. 靜態(tài)資源CDN部署 —— 優(yōu)化網絡請求
    4. 更改資源發(fā)布路徑實現非覆蓋式發(fā)布 —— 平滑升級

    大數相加

     
     
     
     
    1. function add(a, b){ 
    2.    const maxLength = Math.max(a.length, b.length); 
    3.    a = a.padStart(maxLength, 0); 
    4.    b = b.padStart(maxLength, 0); 
    5.    let t = 0; 
    6.    let f = 0;  
    7.    let sum = ""; 
    8.  
    9.    for (let i = maxLength - 1; i >= 0; i--) { 
    10.       t = parseInt(a[i]) + parseInt(b[i]) + f; 
    11.       f = Math.floor(t / 10); 
    12.       sum = `${t % 10}${sum}`; 
    13.    } 
    14.    if (f === 1){ 
    15.       sum = "1" + sum; 
    16.    } 
    17.    return sum; 

    18. 分享名稱:前端基礎知識整理匯總(下)
      本文來源:http://www.dlmjj.cn/article/dpshgpd.html