溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

一些前端基礎(chǔ)知識整理匯總

發(fā)布時間:2021-10-18 11:59:26 來源:億速云 閱讀:133 作者:iii 欄目:web開發(fā)

這篇文章主要介紹“一些前端基礎(chǔ)知識整理匯總”,在日常操作中,相信很多人在一些前端基礎(chǔ)知識整理匯總問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”一些前端基礎(chǔ)知識整理匯總”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

react 生命周期

React v16.0前的生命周期

初始化(initialization)階段

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

constructor()

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

class Checkbox extends React.Component {   constructor(props) {     // ? 這時候還不能使用this     super(props);     // ? 現(xiàn)在開始可以使用this     console.log(props);      // ? {}     console.log(this.props); // ? {}     this.state = {};    } }

為什么super要傳 props?

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

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

其實是 React 在調(diào)用你的構(gòu)造函數(shù)之后,馬上又給實例設(shè)置了一遍 props。

// React 內(nèi)部 class Component {   constructor(props) {     this.props = props; // 初始化 this.props     // ...   } }  // React 內(nèi)部 const instance = new Button(props); instance.props = props; // 給實例設(shè)置 props  // Button類組件 class Button extends React.Component {   constructor(props) {     super(); // ? 我們忘了傳入 props     console.log(props);      // ? {}     console.log(this.props); // ? undefined   } }

掛載(Mounting)階段

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

1. componentWillMount():

在組件掛載到DOM前調(diào)用,且只會被調(diào)用一次。

每一個子組件render之前立即調(diào)用;

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

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

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

  • React 元素。通常通過 JSX 創(chuàng)建。例如,

  • 會被 React 渲染為 DOM 節(jié)點, 會被 React 渲染為自定義組件,無論是

  • 還是 均為 React 元素。

  • 數(shù)組或 fragments。使得 render 方法可以返回多個元素。Fragments 允許你將子列表分組,而無需向 DOM 添加額外節(jié)點。

  • Portals??梢凿秩咀庸?jié)點到不同的 DOM 子樹中。Portal 提供了一種將子節(jié)點渲染到存在于父組件以外的 DOM 節(jié)點的優(yōu)秀的方案。

  • 字符串或數(shù)值類型。它們在 DOM 中會被渲染為文本節(jié)點。

  • 布爾類型或 null。什么都不渲染。

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

3. componentDidMount

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

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

更新(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轉(zhuǎn)換成自己的state,調(diào)用 this.setState()  將不會引起第二次渲染。

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

2. 自身setState

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

生命周期分析

1. componentWillReceiveProps(nextProps)

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

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

2. shouldComponentUpdate(nextProps, nextState)

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

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

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

此方法可以將 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了。

  • 應(yīng)該考慮使用內(nèi)置的 PureComponent 組件,而不是手動編寫 shouldComponentUpdate()。PureComponent 會對  props 和 state 進行淺層比較,并減少了跳過必要更新的可能性。

3. componentWillUpdate(nextProps, nextState)

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

4. render

render同上

5. componentDidUpdate(prevProps, prevState)

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

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

卸載階段

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

componentWillUnmount

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

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

React v16.0 后的生命周期

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

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

為什么要更改生命周期?

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

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. render phase:這個階段決定究竟哪些組件會被更新。

  3. 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  中的生命周期函數(shù)中引入副作用。在 16.3 之前的生命周期很容易引入副作用,所以 16.3 之后引入新的生命周期來限制開發(fā)者引入副作用。

getDerivedStateFromProps(nextProps, prevState)

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

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

特點:

  • 無副作用 。因為是處于 Fiber 的 render 階段,所以有可能會被多次執(zhí)行。所以 API 被設(shè)計為了靜態(tài)函數(shù),無法訪問到實例的方法,也沒有 ref  來操作 DOM,這就避免了實例方法帶來副作用的可能性。但是依舊可以從 props 中獲得方法觸發(fā)副作用,所以在執(zhí)行可能觸發(fā)副作用的函數(shù)前要三思。

  • 只用來更新 state 。其這個生命周期唯一的作用就是從 nextProps 和 prevState 中衍生出一個新的 state。它應(yīng)返回一個對象來更新  state,或者返回null來不更新任何內(nèi)容。

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

getDerivedStateFromProps里面的this為undefined。

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

getSnapshotBeforeUpdate()

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

應(yīng)返回 snapshot 的值(或 null)。

錯誤處理

當渲染過程,生命周期,或子組件的構(gòu)造函數(shù)中拋出錯誤時,會調(diào)用如下方法:

  • static getDerivedStateFromError():此生命周期會在后代組件拋出錯誤后被調(diào)用。它將拋出的錯誤作為參數(shù),并返回一個值以更新  state

  • componentDidCatch():此生命周期在后代組件拋出錯誤后被調(diào)用,它應(yīng)該用于記錄錯誤之類的情況。

它接收兩個參數(shù):

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. error —— 拋出的錯誤。

  3. info —— 帶有 componentStack key 的對象

生命周期比較

16.0 前生命周期

一些前端基礎(chǔ)知識整理匯總

16.0 后生命周期:

一些前端基礎(chǔ)知識整理匯總

參考:

淺析 React v16.3 新生命周期函數(shù)

react 16做了哪些更新

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. react作為一個ui庫,將前端編程由傳統(tǒng)的命令式編程轉(zhuǎn)變?yōu)槁暶魇骄幊?,即所謂的數(shù)據(jù)驅(qū)動視圖。如果直接更新真實dom,比如將生成的html直接采用innerHtml替換,會帶來重繪重排之類的性能問題。為了盡量提高性能,React團隊引入了虛擬dom,即采用js對象來描述dom樹,通過對比前后兩次的虛擬對象,來找到最小的dom操作(vdom  diff),以此提高性能。

  3. 上面提到的reactDom diff,在react 16之前,這個過程我們稱之為stack  reconciler,它是一個遞歸的過程,在樹很深的時候,單次diff時間過長會造成JS線程持續(xù)被占用,用戶交互響應(yīng)遲滯,頁面渲染會出現(xiàn)明顯的卡頓,這在現(xiàn)代前端是一個致命的問題。

  4. 所以為了解決這種問題,react 團隊對整個架構(gòu)進行了調(diào)整,引入了fiber架構(gòu),將以前的stack reconciler替換為fiber  reconciler。采用增量式渲染。引入了任務(wù)優(yōu)先級(expiration)和requestIdleCallback的循環(huán)調(diào)度算法,簡單來說就是將以前的一根筋diff更新,首先拆分成兩個階段:reconciliation與commit;第一個reconciliation階段是可打斷的,被拆分成一個個的小任務(wù)(fiber),在每一偵的渲染空閑期做小任務(wù)diff。然后是commit階段,這個階段是不拆分且不能打斷的,將diff節(jié)點的effectTag一口氣更新到頁面上。

  5. 由于reconciliation是可以被打斷的,且存在任務(wù)優(yōu)先級的問題,所以會導致commit前的一些生命周期函數(shù)多次被執(zhí)行,  如componentWillMount、componentWillReceiveProps 和  componetWillUpdate,但react官方已聲明,在React17中將會移除這三個生命周期函數(shù)。

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

React hooks原理

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

Hooks的本質(zhì)就是閉包和兩級鏈表。

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

hooks 鏈表

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

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

state

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

memoizedState,cursor 是存在哪里的?如何和每個函數(shù)組件一一對應(yīng)的?

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

為什么只能在函數(shù)最外層調(diào)用 Hook?

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

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

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

“Capture Value” 特性是如何產(chǎn)生的?

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

react setState 異步更新

setState 實現(xiàn)原理

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

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

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

所謂“除此之外”,指的是繞過React通過addEventListener直接添加的事件處理函數(shù),還有通過setTimeout/setInterval產(chǎn)生的異步調(diào)用。

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

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

調(diào)用風險

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

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

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

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

React Fiber

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

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

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

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

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

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

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

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

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

React 框架內(nèi)部的運作可以分為 3 層:

  • Virtual DOM 層,描述頁面長什么樣。

  • Reconciler 層,負責調(diào)用組件生命周期方法,進行 Diff 運算等。

  • Renderer 層,根據(jù)不同的平臺,渲染出相應(yīng)的頁面,比較常見的是 ReactDOM 和 ReactNative。

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

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

Reconciler區(qū)別

  • 以前的 Reconciler 被命名為Stack Reconciler。Stack Reconciler  運作的過程是不能被打斷的,必須一條道走到黑;

  • Fiber Reconciler 每執(zhí)行一段時間,都會將控制權(quán)交回給瀏覽器,可以分段執(zhí)行;

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

scheduling(調(diào)度)

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

  • synchronous,與之前的Stack Reconciler操作一樣,同步執(zhí)行

  • task,在next tick之前執(zhí)行

  • animation,下一幀之前執(zhí)行

  • high,在不久的將來立即執(zhí)行

  • low,稍微延遲執(zhí)行也沒關(guān)系

  • offscreen,下一次render時或scroll時才執(zhí)行

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

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

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

  • 階段二,將需要更新的節(jié)點一次過批量更新,這個過程不能被打斷。

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

參考:

React Fiber 原理介紹

React Fiber

HOC 與render props區(qū)別

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

實現(xiàn)方式:

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

中,被傳遞的這些子組件最終都會出現(xiàn)在輸出結(jié)果中。

// 定義 const RenderProps = props => <div>    {props.children(props)} </div>  // 調(diào)用 <RenderProps>     {() => <>Hello RenderProps</>} </RenderProps>

2.通過props中的任何函數(shù), 自行定義傳入內(nèi)容

// 定義 const LoginForm = props => {   const flag = false;   const allProps = { flag, ...props };    if (flag) {     return <>{props.login(allProps)}</>   } else {     return <>{props.notLogin(allProps)}</>   } }  // 調(diào)用 <LoginForm   login={() => <h2>LOGIN</h2>}   noLogin={() => <h2>NOT LOGIN</h2>} />

優(yōu)點:

1、支持ES6

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

3、不會產(chǎn)生無用的組件加深層級

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

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

class Home extends React.Component {   // UI }  export default Connect()(Home);

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

優(yōu)點:

1、支持ES6

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

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

缺點:

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

2、多個組件嵌套,容易產(chǎn)生同樣名稱的props

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

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

React 通信

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

  • 父向子通信:傳入props

  • 子向父通信:父組件向子組件傳一個函數(shù),然后通過這個函數(shù)的回調(diào),拿到子組件傳過來的值

  • 父向?qū)O通信:利用context傳值。React.createContext()

  • 兄弟間通信:

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

2、用一些全局機制去實現(xiàn)通信,比如redux等

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

react合成事件

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

為什么要使用合成事件?

1.進行瀏覽器兼容,實現(xiàn)更好的跨平臺

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

2.避免垃圾回收

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

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

實現(xiàn)原理

在 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í)行的結(jié)果得到的并不是真正的 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事件處理程序或同步生命周期方法中進行多少次調(diào)用,它都將被批處理成一個更新, 最終只有一次重新渲染。

虛擬 DOM 與 原生 DOM

如果沒有 Virtual DOM,就需要直接操作原生 DOM。在一個大型列表所有數(shù)據(jù)都變了的情況下,直接重置  innerHTML還算合理,但是,只有一行數(shù)據(jù)發(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 操作都是和整個界面的大小相關(guān),但 Virtual DOM 的計算量只有  js 計算和界面大小相關(guān),DOM 操作是和數(shù)據(jù)的變動量相關(guān)。

一些前端基礎(chǔ)知識整理匯總

虛擬 DOM 與 MVVM

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

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

  • 臟檢查:scope digest(watcher count) ) + 必要 DOM 更新

  • 依賴收集:重新收集依賴(data change) ) + 必要 DOM 更新

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

性能比較

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

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

  • 初始渲染:Virtual DOM > 臟檢查 >= 依賴收集

  • 小量數(shù)據(jù)更新:依賴收集 >> Virtual DOM + 優(yōu)化 > 臟檢查(無法優(yōu)化)> Virtual DOM 無優(yōu)化

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

diff 算法

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

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

  • 用 JS 對象的方式來表示 DOM 樹的結(jié)構(gòu),然后根據(jù)這個對象構(gòu)建出真實的 DOM 樹,插到文檔中。

  • 當狀態(tài)變更的時候,重新構(gòu)造一棵新的對象樹。然后用新的樹和舊的樹進行比較,記錄兩棵樹的差異,  最后把所記錄的差異應(yīng)用到所構(gòu)建的真正的DOM樹上,視圖更新。

diff 策略

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

  • React 通過分層求異的策略,對 tree diff 進行算法優(yōu)化;

  • React 通過相同類生成相似樹形結(jié)構(gòu),不同類生成不同樹形結(jié)構(gòu)的策略,對 component diff 進行算法優(yōu)化;

  • React 通過設(shè)置唯一 key的策略,對 element diff 進行算法優(yōu)化;

tree diff(層級比較)

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

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

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

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

  • 先進行樹結(jié)構(gòu)的層級比較,對同一個父節(jié)點下的所有子節(jié)點進行比較;

  • 接著看節(jié)點是什么類型的,是組件就做 Component Diff;

  • 如果節(jié)點是標簽或者元素,就做 Element Diff;

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

component diff(組件比較)

  • 如果是同一類型的組件,按照原策略繼續(xù)比較 virtual DOM tree。

  • 如果不是,則將該組件判斷為 dirty component,替換整個組件下的所有子節(jié)點。舉個例子,當一個元素從 變成

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

對于兩個不同類型但結(jié)構(gòu)相似的組件,不會比較二者的結(jié)構(gòu),而且替換整個組件的所有內(nèi)容。不同類型的 component 是很少存在相似 DOM tree  的機會,因此這種極端因素很難在實現(xiàn)開發(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 類型,在新集合里也有,但對應(yīng)的 element 不同則不能直接復用和更新,需要執(zhí)行刪除操作,或者老  component 不在新集合里的,也需要執(zhí)行刪除操作。

<ul>   <li>Duke</li>   <li>Villanova</li> </ul>  <ul>   <li>Connecticut</li>   <li>Duke</li>   <li>Villanova</li> </ul>

React 并不會意識到應(yīng)該保留<li>Duke</li>和<li>Villanova</li>,而是會重建每一個子元素,不會進行移動 DOM 操作。

key 優(yōu)化

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

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

  • 在開發(fā)過程中,盡量減少類似將最后一個節(jié)點移動到列表首部的操作,當節(jié)點數(shù)量過大或更新操作過于頻繁時,在一定程度上會影響 React 的渲染性能。

  • key 不需要全局唯一,但在列表中需要保持唯一。

  • Key 應(yīng)該具有穩(wěn)定,可預測,以及列表內(nèi)唯一的特質(zhì)。不穩(wěn)定的 key(比如通過 Math.random() 生成的)會導致許多組件實例和 DOM  節(jié)點被不必要地重新創(chuàng)建,這可能導致性能下降和子組件中的狀態(tài)丟失。

react與vue區(qū)別

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

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

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

2. 數(shù)據(jù)流不同

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

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

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

3. HoC和mixins

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

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

4. 模板渲染方式的不同

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

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

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

5. 渲染過程不同

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

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

6. 框架本質(zhì)不同

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

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

性能優(yōu)化

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

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

2. 無阻塞

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

3. 壓縮文件

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

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

JavaScript:UglifyPlugin

CSS :MiniCssExtractPlugin

HTML:HtmlWebpackPlugin

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

4. 圖片優(yōu)化

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. 圖片懶加載

  3. 響應(yīng)式圖片:瀏覽器根據(jù)屏幕大小自動加載合適的圖片。

  4. 降低圖片質(zhì)量:方法有兩種,一是通過 webpack 插件 image-webpack-loader,二是通過在線網(wǎng)站進行壓縮。

5. 減少重繪重排

  • 降低 CSS 選擇器的復雜性

  • 使用 transform 和 opacity 屬性更改來實現(xiàn)動畫

  • 用 JavaScript 修改樣式時,最好不要直接寫樣式,而是替換 class 來改變樣式。

  • 如果要對 DOM 元素執(zhí)行一系列操作,可以將 DOM  元素脫離文檔流,修改完成后,再將它帶回文檔。推薦使用隱藏元素(display:none)或文檔碎片(DocumentFragement),都能很好的實現(xiàn)這個方案。

6. 使用 requestAnimationFrame 來實現(xiàn)視覺變化

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

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

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

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

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

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

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

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

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. DNS域名解析;

  3. 建立TCP連接(三次握手);

  4. 發(fā)送HTTP請求;

  5. 服務(wù)器處理請求;

  6. 返回響應(yīng)結(jié)果;

  7. 關(guān)閉TCP連接(四次握手);

  8. 瀏覽器解析HTML;

  9. 瀏覽器布局渲染;

1. DNS域名解析:拿到服務(wù)器ip

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

2. 建立TCP鏈接:客戶端鏈接服務(wù)器

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

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

SYN &mdash;&mdash; 用于初如化一個連接的序列號。

ACK &mdash;&mdash; 確認,使得確認號有效。

RST &mdash;&mdash; 重置連接。

FIN &mdash;&mdash; 該報文段的發(fā)送方已經(jīng)結(jié)束向?qū)Ψ桨l(fā)送數(shù)據(jù)。

  • 客戶端:“你好,在家不。” -- SYN

  • 服務(wù)端:“在的,你來吧。” -- SYN + ACK

  • 客戶端:“好嘞?!?-- ACK

3. 發(fā)送HTTP請求

4. 服務(wù)器處理請求

5. 返回響應(yīng)結(jié)果

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

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

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

  • 客戶端:“兄弟,我這邊沒數(shù)據(jù)要傳了,咱關(guān)閉連接吧。” -- FIN + seq

  • 服務(wù)端:“收到,我看看我這邊有木有數(shù)據(jù)了。” -- ACK + seq + ack

  • 服務(wù)端:“兄弟,我這邊也沒數(shù)據(jù)要傳你了,咱可以關(guān)閉連接了?!?- 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,會出現(xiàn)阻塞問題。

8. 瀏覽器渲染頁面

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

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

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

參考:

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

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

前端路由

什么是路由

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

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

前端路由

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

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

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

  • SPA 無法記住用戶的操作記錄,無論是刷新、前進還是后退,都無法展示用戶真實的期望內(nèi)容。

  • SPA 中雖然由于業(yè)務(wù)的不同會有多種頁面展示形式,但只有一個 url,對 SEO 不友好,不方便搜索引擎進行收錄。

前端路由就是為了解決上述問題而出現(xiàn)的。

前端路由的實現(xiàn)方式

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

1. Hash模式

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

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

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

2.History 模式

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

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

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

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

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

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

  • 調(diào)用history.pushState()或history.replaceState()不會觸發(fā)popstate事件。只有在做出瀏覽器動作時,才會觸發(fā)該事件,例如執(zhí)行history.back()或history.forward()后觸發(fā)  window.onpopstate事件。

// 歷史棧改變 window.onpopstate = function() { ... }

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

兩種模式對比

一些前端基礎(chǔ)知識整理匯總

前端路由實踐

vue-router/react-router 都是基于前端路由的原理實現(xiàn)的~

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

  • browserHistory: 使用瀏覽器中的History API 用于處理 URL。history 在 DOM 上的實現(xiàn),用于支持 HTML5  history API 的瀏覽器。

  • hashHistory: 使用 URL 中的 hash(#)部分去創(chuàng)建路由。history 在 DOM 上的實現(xiàn),用于舊版瀏覽器。

  • createMemoryHistory: 不會在地址欄被操作或讀取,history 在內(nèi)存上的實現(xiàn),用于測試或非 DOM 環(huán)境(例如 React  Native)。

Babel Plugin與preset區(qū)別

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

  • 原始代碼 --> [Babel Plugin] --> 轉(zhuǎn)換后的代碼

Plugin

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

Preset

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

Plugin與Preset執(zhí)行順序

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

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. 先執(zhí)行完所有Plugin,再執(zhí)行Preset。

  3. 多個Plugin,按照聲明次序順序執(zhí)行。

  4. 多個Preset,按照聲明次序逆序執(zhí)行。

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

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. Plugin:transform-react-jsx、transform-async-to-generator

  3. Preset:es2016、es2015

{   "presets": [      "es2015",     "es2016"       ],   "plugins": [      "transform-react-jsx",     "transform-async-to-generator"   ] }

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

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

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

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. 先部署頁面,再部署資源:在二者部署的時間間隔內(nèi),如果有用戶訪問頁面,就會在新的頁面結(jié)構(gòu)中加載舊的資源,并且把這個舊版本的資源當做新版本緩存起來,其結(jié)果就是:用戶訪問到了一個樣式錯亂的頁面,除非手動刷新,否則在資源緩存過期之前,頁面會一直執(zhí)行錯誤。

  3. 先部署資源,再部署頁面:在部署時間間隔之內(nèi),有舊版本資源本地緩存的用戶訪問網(wǎng)站,由于請求的頁面是舊版本的,資源引用沒有改變,瀏覽器將直接使用本地緩存,這種情況下頁面展現(xiàn)正常;但沒有本地緩存或者緩存過期的用戶訪問網(wǎng)站,就會出現(xiàn)舊版本頁面加載新版本資源的情況,導致頁面執(zhí)行錯誤,但當頁面完成部署,這部分用戶再次訪問頁面又會恢復正常了。

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

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

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

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

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. 配置超長時間的本地緩存 &mdash;&mdash; 節(jié)省帶寬,提高性能

  3. 采用內(nèi)容摘要作為緩存更新依據(jù) &mdash;&mdash; 精確的緩存控制

  4. 靜態(tài)資源CDN部署 &mdash;&mdash; 優(yōu)化網(wǎng)絡(luò)請求

  5. 更改資源發(fā)布路徑實現(xiàn)非覆蓋式發(fā)布 &mdash;&mdash; 平滑升級

大數(shù)相加

function add(a, b){    const maxLength = Math.max(a.length, b.length);    a = a.padStart(maxLength, 0);    b = b.padStart(maxLength, 0);    let t = 0;    let f = 0;     let sum = "";     for (let i = maxLength - 1; i >= 0; i--) {       t = parseInt(a[i]) + parseInt(b[i]) + f;       f = Math.floor(t / 10);       sum = `${t % 10}${sum}`;    }    if (f === 1){       sum = "1" + sum;    }    return sum; }

斐波那契數(shù)列求和

function fib(n) {     if (n <= 0) {         return 0;     }     let n1 = 1;     let n2 = 1;     let sum = 1;     for(let i = 3; i <= n; i++) {         [n1, n2] = [n2, sum];         sum = n1 + n2;     }     return sum; };

到此,關(guān)于“一些前端基礎(chǔ)知識整理匯總”的學習就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續(xù)學習更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI