這篇文章主要介紹“一些前端基礎(chǔ)知識(shí)整理匯總”,在日常操作中,相信很多人在一些前端基礎(chǔ)知識(shí)整理匯總問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”一些前端基礎(chǔ)知識(shí)整理匯總”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!
10年積累的做網(wǎng)站、網(wǎng)站建設(shè)經(jīng)驗(yàn),可以快速應(yīng)對(duì)客戶對(duì)網(wǎng)站的新想法和需求。提供各種問(wèn)題對(duì)應(yīng)的解決方案。讓選擇我們的客戶得到更好、更有力的網(wǎng)絡(luò)服務(wù)。我雖然不認(rèn)識(shí)你,你也不認(rèn)識(shí)我。但先網(wǎng)站制作后付款的網(wǎng)站建設(shè)流程,更有烏蘇免費(fèi)網(wǎng)站建設(shè)讓你可以放心的選擇與我們合作。
react 生命周期
React v16.0前的生命周期
初始化(initialization)階段
此階段只有一個(gè)生命周期方法:constructor。
constructor()
用來(lái)做一些組件的初始化工作,如定義this.state的初始內(nèi)容。如果不初始化 state 或不進(jìn)行方法綁定,則不需要為 React 組件實(shí)現(xiàn)構(gòu)造函數(shù)。為什么必須先調(diào)用super(props)?因?yàn)樽宇愖约旱膖his對(duì)象,必須先通過(guò)父類的構(gòu)造函數(shù)完成塑造,得到與父類同樣的實(shí)例屬性和方法,然后再對(duì)其進(jìn)行加工,加上子類自己的實(shí)例屬性和方法。如果不調(diào)用super方法,子類就得不到this對(duì)象。
class Checkbox extends React.Component { constructor(props) { // ? 這時(shí)候還不能使用this super(props); // ? 現(xiàn)在開始可以使用this console.log(props); // ? {} console.log(this.props); // ? {} this.state = {}; } }
為什么super要傳 props?
把 props 傳進(jìn) super 是必要的,這使得基類 React.Component 可以初始化 this.props。
然而,即便在調(diào)用 super() 時(shí)沒(méi)有傳入 props 參數(shù),你依然能夠在 render 和其它方法中訪問(wèn) this.props。
其實(shí)是 React 在調(diào)用你的構(gòu)造函數(shù)之后,馬上又給實(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í)例設(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)用,且只會(huì)被調(diào)用一次。
每一個(gè)子組件render之前立即調(diào)用;
在此方法調(diào)用this.setState不會(huì)引起組件重新渲染,也可以把寫在這邊的內(nèi)容提前到constructor()中。
2. render(): class 組件唯一必須實(shí)現(xiàn)的方法
當(dāng) render 被調(diào)用時(shí),它會(huì)檢查 this.props 和 this.state 的變化并返回以下類型之一:
React 元素。通常通過(guò) JSX 創(chuàng)建。例如,
會(huì)被 React 渲染為 DOM 節(jié)點(diǎn), 會(huì)被 React 渲染為自定義組件,無(wú)論是
還是 均為 React 元素。
數(shù)組或 fragments。使得 render 方法可以返回多個(gè)元素。Fragments 允許你將子列表分組,而無(wú)需向 DOM 添加額外節(jié)點(diǎn)。
Portals。可以渲染子節(jié)點(diǎn)到不同的 DOM 子樹中。Portal 提供了一種將子節(jié)點(diǎn)渲染到存在于父組件以外的 DOM 節(jié)點(diǎn)的優(yōu)秀的方案。
字符串或數(shù)值類型。它們?cè)?DOM 中會(huì)被渲染為文本節(jié)點(diǎn)。
布爾類型或 null。什么都不渲染。
render() 函數(shù)應(yīng)該為純函數(shù),這意味著在不修改組件 state 的情況下,每次調(diào)用時(shí)都返回相同的結(jié)果,并且它不會(huì)直接與瀏覽器交互。不能在里面執(zhí)行this.setState,會(huì)有改變組件狀態(tài)的副作用。
3. componentDidMount
會(huì)在組件掛載后(插入 DOM 樹中)立即調(diào)用, 且只會(huì)被調(diào)用一次。依賴于 DOM 節(jié)點(diǎn)的初始化應(yīng)該放在這里。
render之后并不會(huì)立即調(diào)用,而是所有的子組件都render完之后才會(huì)調(diào)用。
更新(update)階段
此階段生命周期方法:componentWillReceiveProps => shouldComponentUpdate => componentWillUpdate => render => componentDidUpdate。
react組件更新機(jī)制
setState引起的state更新或父組件重新render引起的props更新,更新后的state和props相對(duì)之前無(wú)論是否有變化,都將引起子組件的重新render。
1. 父組件重新render
直接重新渲染。每當(dāng)父組件重新render導(dǎo)致的重傳props,子組件將直接跟著重新渲染,無(wú)論props是否有變化。可通過(guò)shouldComponentUpdate方法優(yōu)化。
更新state再渲染。在componentWillReceiveProps方法中,將props轉(zhuǎn)換成自己的state,調(diào)用 this.setState() 將不會(huì)引起第二次渲染。
因?yàn)閏omponentWillReceiveProps中判斷props是否變化了,若變化了,this.setState將引起state變化,從而引起render,此時(shí)就沒(méi)必要再做第二次因重傳props引起的render了,不然重復(fù)做一樣的渲染了。
2. 自身setState
組件本身調(diào)用setState,無(wú)論state有沒(méi)有變化。可通過(guò)shouldComponentUpdate方法優(yōu)化。
生命周期分析
1. componentWillReceiveProps(nextProps)
此方法只調(diào)用于props引起的組件更新過(guò)程中,響應(yīng) Props 變化之后進(jìn)行更新的唯一方式。
參數(shù)nextProps是父組件傳給當(dāng)前組件的新props。根據(jù)nextProps和this.props來(lái)判斷重傳的props是否改變,以及做相應(yīng)的處理。
2. shouldComponentUpdate(nextProps, nextState)
根據(jù) shouldComponentUpdate() 的返回值,判斷 React 組件的輸出是否受當(dāng)前 state 或 props 更改的影響。默認(rèn)行為是 state 每次發(fā)生變化組件都會(huì)重新渲染。
當(dāng) props 或 state 發(fā)生變化時(shí),shouldComponentUpdate() 會(huì)在渲染執(zhí)行之前被調(diào)用。返回值默認(rèn)為 true。
首次渲染或使用 forceUpdate() 時(shí)不會(huì)調(diào)用該方法。
此方法可以將 this.props 與 nextProps 以及 this.state 與nextState 進(jìn)行比較,返回true時(shí)當(dāng)前組件將繼續(xù)執(zhí)行更新過(guò)程,返回false則跳過(guò)更新,以此可用來(lái)減少組件的不必要渲染,優(yōu)化組件性能。
請(qǐng)注意,返回 false 并不會(huì)阻止子組件在 state 更改時(shí)重新渲染。
如果在componentWillReceiveProps()中執(zhí)行了this.setState,更新state,但在render前(如shouldComponentUpdate,componentWillUpdate),this.state依然指向更新前的state,不然nextState及當(dāng)前組件的this.state的對(duì)比就一直是true了。
應(yīng)該考慮使用內(nèi)置的 PureComponent 組件,而不是手動(dòng)編寫 shouldComponentUpdate()。PureComponent 會(huì)對(duì) props 和 state 進(jìn)行淺層比較,并減少了跳過(guò)必要更新的可能性。
3. componentWillUpdate(nextProps, nextState)
此方法在調(diào)用render方法前執(zhí)行,在這邊可執(zhí)行一些組件更新發(fā)生前的工作,一般較少用。
4. render
render同上
5. componentDidUpdate(prevProps, prevState)
此方法在組件更新后立即調(diào)用,可以操作組件更新的DOM。
prevProps和prevState這兩個(gè)參數(shù)指的是組件更新前的props和state。
卸載階段
此階段只有一個(gè)生命周期方法:componentWillUnmount
componentWillUnmount
此方法在組件被卸載前調(diào)用,可以在這里執(zhí)行一些清理工作,比如清楚組件中使用的定時(shí)器,清楚componentDidMount中手動(dòng)創(chuàng)建的DOM元素等,以避免引起內(nèi)存泄漏。
componentWillUnmount() 中不應(yīng)調(diào)用 setState(),因?yàn)樵摻M件將永遠(yuǎn)不會(huì)重新渲染。組件實(shí)例卸載后,將永遠(yuǎn)不會(huì)再掛載它。
React v16.0 后的生命周期
React v16.0剛推出的時(shí)候,增加了一個(gè)componentDidCatch生命周期函數(shù),這只是一個(gè)增量式修改,完全不影響原有生命周期函數(shù);
React v16.3,引入了兩個(gè)新的生命周期:getDerivedStateFromProps,getSnapshotBeforeUpdate, 廢棄掉componentWillMount、componentWillReceiveProps 以及 componentWillUpdate 三個(gè)周期(直到React 17前還可以使用,只是會(huì)有一個(gè)警告)。
為什么要更改生命周期?
生命周期函數(shù)的更改是因?yàn)?16.3 采用了 Fiber 架構(gòu),在新的 Fiber 架構(gòu)中,組件的更新分為了兩個(gè)階段:
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
render phase:這個(gè)階段決定究竟哪些組件會(huì)被更新。
commit phase:這個(gè)階段是 React 開始執(zhí)行更新(比如插入,移動(dòng),刪除節(jié)點(diǎn))。
commit phase 的執(zhí)行很快,但是真實(shí) DOM 的更新很慢,所以 React 在更新的時(shí)候會(huì)暫停再恢復(fù)組件的更新以免長(zhǎng)時(shí)間的阻塞瀏覽器,這就意味著 render phase 可能會(huì)被執(zhí)行多次(因?yàn)橛锌赡鼙淮驍嘣僦匦聢?zhí)行)。
constructor
componentWillMount
componentWillReceiveProps
componentWillUpdate
getDerivedStateFromProps
shouldComponentUpdate
render
這些生命周期都屬于 render phase,render phase 可能被多次執(zhí)行,所以要避免在 render phase 中的生命周期函數(shù)中引入副作用。在 16.3 之前的生命周期很容易引入副作用,所以 16.3 之后引入新的生命周期來(lái)限制開發(fā)者引入副作用。
getDerivedStateFromProps(nextProps, prevState)
React v16.3中,static getDerivedStateFromProps只在組件創(chuàng)建和由父組件引發(fā)的更新中調(diào)用。如果不是由父組件引發(fā),那么getDerivedStateFromProps也不會(huì)被調(diào)用,如自身setState引發(fā)或者forceUpdate引發(fā)。
在React v16.4中改正了這一點(diǎn),static getDerivedStateFromProps會(huì)在調(diào)用 render 方法之前調(diào)用,并且在初始掛載及后續(xù)更新時(shí)都會(huì)被調(diào)用。
特點(diǎn):
無(wú)副作用 。因?yàn)槭翘幱?Fiber 的 render 階段,所以有可能會(huì)被多次執(zhí)行。所以 API 被設(shè)計(jì)為了靜態(tài)函數(shù),無(wú)法訪問(wèn)到實(shí)例的方法,也沒(méi)有 ref 來(lái)操作 DOM,這就避免了實(shí)例方法帶來(lái)副作用的可能性。但是依舊可以從 props 中獲得方法觸發(fā)副作用,所以在執(zhí)行可能觸發(fā)副作用的函數(shù)前要三思。
只用來(lái)更新 state 。其這個(gè)生命周期唯一的作用就是從 nextProps 和 prevState 中衍生出一個(gè)新的 state。它應(yīng)返回一個(gè)對(duì)象來(lái)更新 state,或者返回null來(lái)不更新任何內(nèi)容。
getDerivedStateFromProps前面要加上static保留字,聲明為靜態(tài)方法,不然會(huì)被react忽略掉。
getDerivedStateFromProps里面的this為undefined。
static靜態(tài)方法只能Class來(lái)調(diào)用,而實(shí)例是不能調(diào)用,所以React Class組件中,靜態(tài)方法getDerivedStateFromProps無(wú)權(quán)訪問(wèn)Class實(shí)例的this,即this為undefined。
getSnapshotBeforeUpdate()
getSnapshotBeforeUpdate() 只會(huì)調(diào)用一次,在最近一次渲染輸出(提交到 DOM 節(jié)點(diǎn))之前調(diào)用,,所以在這個(gè)生命周期能夠獲取這一次更新前的 DOM 的信息。此生命周期的任何返回值將作為 componentDidUpdate() 的第三個(gè)參數(shù) “snapshot” 參數(shù)傳遞, 否則componentDidUpdate的第三個(gè)參數(shù)將為 undefined。
應(yīng)返回 snapshot 的值(或 null)。
錯(cuò)誤處理
當(dāng)渲染過(guò)程,生命周期,或子組件的構(gòu)造函數(shù)中拋出錯(cuò)誤時(shí),會(huì)調(diào)用如下方法:
static getDerivedStateFromError():此生命周期會(huì)在后代組件拋出錯(cuò)誤后被調(diào)用。它將拋出的錯(cuò)誤作為參數(shù),并返回一個(gè)值以更新 state
componentDidCatch():此生命周期在后代組件拋出錯(cuò)誤后被調(diào)用,它應(yīng)該用于記錄錯(cuò)誤之類的情況。
它接收兩個(gè)參數(shù):
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
error —— 拋出的錯(cuò)誤。
info —— 帶有 componentStack key 的對(duì)象
生命周期比較
16.0 前生命周期
16.0 后生命周期:
參考:
淺析 React v16.3 新生命周期函數(shù)
react 16做了哪些更新
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
react作為一個(gè)ui庫(kù),將前端編程由傳統(tǒng)的命令式編程轉(zhuǎn)變?yōu)槁暶魇骄幊蹋此^的數(shù)據(jù)驅(qū)動(dòng)視圖。如果直接更新真實(shí)dom,比如將生成的html直接采用innerHtml替換,會(huì)帶來(lái)重繪重排之類的性能問(wèn)題。為了盡量提高性能,React團(tuán)隊(duì)引入了虛擬dom,即采用js對(duì)象來(lái)描述dom樹,通過(guò)對(duì)比前后兩次的虛擬對(duì)象,來(lái)找到最小的dom操作(vdom diff),以此提高性能。
上面提到的reactDom diff,在react 16之前,這個(gè)過(guò)程我們稱之為stack reconciler,它是一個(gè)遞歸的過(guò)程,在樹很深的時(shí)候,單次diff時(shí)間過(guò)長(zhǎng)會(huì)造成JS線程持續(xù)被占用,用戶交互響應(yīng)遲滯,頁(yè)面渲染會(huì)出現(xiàn)明顯的卡頓,這在現(xiàn)代前端是一個(gè)致命的問(wèn)題。
所以為了解決這種問(wèn)題,react 團(tuán)隊(duì)對(duì)整個(gè)架構(gòu)進(jìn)行了調(diào)整,引入了fiber架構(gòu),將以前的stack reconciler替換為fiber reconciler。采用增量式渲染。引入了任務(wù)優(yōu)先級(jí)(expiration)和requestIdleCallback的循環(huán)調(diào)度算法,簡(jiǎn)單來(lái)說(shuō)就是將以前的一根筋diff更新,首先拆分成兩個(gè)階段:reconciliation與commit;第一個(gè)reconciliation階段是可打斷的,被拆分成一個(gè)個(gè)的小任務(wù)(fiber),在每一偵的渲染空閑期做小任務(wù)diff。然后是commit階段,這個(gè)階段是不拆分且不能打斷的,將diff節(jié)點(diǎn)的effectTag一口氣更新到頁(yè)面上。
由于reconciliation是可以被打斷的,且存在任務(wù)優(yōu)先級(jí)的問(wèn)題,所以會(huì)導(dǎo)致commit前的一些生命周期函數(shù)多次被執(zhí)行, 如componentWillMount、componentWillReceiveProps 和 componetWillUpdate,但react官方已聲明,在React17中將會(huì)移除這三個(gè)生命周期函數(shù)。
由于每次喚起更新是從根節(jié)點(diǎn)(RootFiber)開始,為了更好的節(jié)點(diǎn)復(fù)用與性能優(yōu)化。在react中始終存workInprogressTree(future vdom) 與 oldTree(current vdom)兩個(gè)鏈表,兩個(gè)鏈表相互引用。這無(wú)形中又解決了另一個(gè)問(wèn)題,當(dāng)workInprogressTree生成報(bào)錯(cuò)時(shí),這時(shí)也不會(huì)導(dǎo)致頁(yè)面渲染崩潰,而只是更新失敗,頁(yè)面仍然還在。
React hooks原理
在React 16前,函數(shù)式組件不能擁有狀態(tài)管理?因?yàn)?6以前只有類組件有對(duì)應(yīng)的實(shí)例,而16以后Fiber 架構(gòu)的出現(xiàn),讓每一個(gè)節(jié)點(diǎn)都擁有對(duì)應(yīng)的實(shí)例,也就擁有了保存狀態(tài)的能力。
Hooks的本質(zhì)就是閉包和兩級(jí)鏈表。
閉包是指有權(quán)訪問(wèn)另一個(gè)函數(shù)作用域中變量或方法的函數(shù),創(chuàng)建閉包的方式就是在一個(gè)函數(shù)內(nèi)創(chuàng)建閉包函數(shù),通過(guò)閉包函數(shù)訪問(wèn)這個(gè)函數(shù)的局部變量, 利用閉包可以突破作用鏈域的特性,將函數(shù)內(nèi)部的變量和方法傳遞到外部。
hooks 鏈表
一個(gè)組件包含的hooks 以鏈表的形式存儲(chǔ)在fiber節(jié)點(diǎn)的memoizedState屬性上,currentHook鏈表就是當(dāng)前正在遍歷的fiber節(jié)點(diǎn)的。nextCurrentHook 就是即將被添加到正在遍歷fiber節(jié)點(diǎn)的hooks的新鏈表。
let currentHook: Hook | null = null; let nextCurrentHook: Hook | null = null; type Hooks = { memoizedState: any, // 指向當(dāng)前渲染節(jié)點(diǎn) Fiber baseState: any, // 初始化 initialState, 最新的state baseUpdate: Update<any> | null, // 當(dāng)前需要更新的 Update ,每次更新完之后,會(huì)賦值上一個(gè) update,方便 react 在渲染錯(cuò)誤的邊緣,數(shù)據(jù)回溯 queue: UpdateQueue<any> | null,// 可以讓state變化的,即update或dispach產(chǎn)生的update next: Hook | null, // link 到下一個(gè) hooks }
state
其實(shí)state不是hooks獨(dú)有的,類操作的setState也存在。
memoizedState,cursor 是存在哪里的?如何和每個(gè)函數(shù)組件一一對(duì)應(yīng)的?
react 會(huì)生成一棵組件樹(或Fiber 單鏈表),樹中每個(gè)節(jié)點(diǎn)對(duì)應(yīng)了一個(gè)組件,hooks 的數(shù)據(jù)就作為組件的一個(gè)信息,存儲(chǔ)在這些節(jié)點(diǎn)上,伴隨組件一起出生,一起死亡。
為什么只能在函數(shù)最外層調(diào)用 Hook?
memoizedState 是按 hook定義的順序來(lái)放置數(shù)據(jù)的,如果 hook 順序變化,memoizedState 并不會(huì)感知到。
自定義的 Hook 是如何影響使用它的函數(shù)組件的?
共享同一個(gè) memoizedState,共享同一個(gè)順序。
“Capture Value” 特性是如何產(chǎn)生的?
每一次 ReRender 的時(shí)候,都是重新去執(zhí)行函數(shù)組件了,對(duì)于之前已經(jīng)執(zhí)行過(guò)的函數(shù)組件,并不會(huì)做任何操作。
react setState 異步更新
setState 實(shí)現(xiàn)原理
setState 通過(guò)一個(gè)隊(duì)列機(jī)制來(lái)實(shí)現(xiàn) state 更新,當(dāng)執(zhí)行 setState() 時(shí),會(huì)將需要更新的 state 淺合并后放入 狀態(tài)隊(duì)列,而不會(huì)立即更新 state,隊(duì)列機(jī)制可以高效的批量更新 state。如果不通過(guò)setState,直接修改this.state 的值,則不會(huì)放入狀態(tài)隊(duì)列,當(dāng)下一次調(diào)用 setState 對(duì)狀態(tài)隊(duì)列進(jìn)行合并時(shí),之前對(duì) this.state 的修改將會(huì)被忽略,造成無(wú)法預(yù)知的錯(cuò)誤。
setState()有的同步有的異步?
在React中, 如果是由React引發(fā)的事件處理(比如通過(guò)onClick引發(fā)的事件處理),調(diào)用setState不會(huì)同步更新this.state,除此之外的setState調(diào)用會(huì)同步執(zhí)行this.state 。
所謂“除此之外”,指的是繞過(guò)React通過(guò)addEventListener直接添加的事件處理函數(shù),還有通過(guò)setTimeout/setInterval產(chǎn)生的異步調(diào)用。
原因: 在React的setState函數(shù)實(shí)現(xiàn)中,會(huì)根據(jù)一個(gè)變量isBatchingUpdates判斷是直接更新this.state還是放到隊(duì)列中回頭再說(shuō),而isBatchingUpdates默認(rèn)是false,也就表示setState會(huì)同步更新this.state,但是,有一個(gè)函數(shù)batchedUpdates,這個(gè)函數(shù)會(huì)把isBatchingUpdates修改為true,而當(dāng)React在調(diào)用事件處理函數(shù)之前就會(huì)調(diào)用這個(gè)batchedUpdates,造成的后果,就是由React控制的事件處理過(guò)程setState不會(huì)同步更新this.state。
setState的“異步”并不是說(shuō)內(nèi)部由異步代碼實(shí)現(xiàn),其實(shí)本身執(zhí)行的過(guò)程和代碼都是同步的,只是合成事件和鉤子函數(shù)的調(diào)用順序在更新之前,導(dǎo)致在合成事件和鉤子函數(shù)中沒(méi)法立馬拿到更新后的值,形式了所謂的“異步”,可以通過(guò)第二個(gè)參數(shù) setState(partialState, callback) 中的callback拿到更新后的結(jié)果。
調(diào)用風(fēng)險(xiǎn)
當(dāng)調(diào)用 setState 時(shí),實(shí)際上是會(huì)執(zhí)行 enqueueSetState 方法,并會(huì)對(duì) partialState 及 _pendingStateQueue 隊(duì)列進(jìn)行合并操作,最終通過(guò) enqueueUpdate 執(zhí)行 state 更新。
而 performUpdateIfNecessary 獲取 _pendingElement、 _pendingStateQueue、_pendingForceUpdate,并調(diào)用 reaciveComponent 和 updateComponent 來(lái)進(jìn)行組件更新。
但,如果在 shouldComponentUpdate 或 componentWillUpdate 方法里調(diào)用 this.setState 方法,就會(huì)造成崩潰。
這是因?yàn)樵?shouldComponentUpdate 或 componentWillUpdate 方法里調(diào)用 this.setState 時(shí),this._pendingStateQueue!=null,則 performUpdateIfNecessary 方法就會(huì)調(diào)用 updateComponent 方法進(jìn)行組件更新,而 updateComponent 方法又會(huì)調(diào)用 shouldComponentUpdate和componentWillUpdate 方法,因此造成循環(huán)調(diào)用,使得瀏覽器內(nèi)存占滿后崩潰。
React Fiber
掉幀:在頁(yè)面元素很多,且需要頻繁刷新的場(chǎng)景下,React 15 會(huì)出現(xiàn)掉幀的現(xiàn)象,其根本原因,是大量的同步計(jì)算任務(wù)阻塞了瀏覽器的 UI 渲染。
默認(rèn)情況下,JS 運(yùn)算、頁(yè)面布局和頁(yè)面繪制都是運(yùn)行在瀏覽器的主線程當(dāng)中,他們之間是互斥的關(guān)系。
如果 JS 運(yùn)算持續(xù)占用主線程,頁(yè)面就沒(méi)法得到及時(shí)的更新。
當(dāng)我們調(diào)用setState更新頁(yè)面的時(shí)候,React 會(huì)遍歷應(yīng)用的所有節(jié)點(diǎn),計(jì)算出差異,然后再更新 UI,整個(gè)過(guò)程不能被打斷。
如果頁(yè)面元素很多,整個(gè)過(guò)程占用的時(shí)機(jī)就可能超過(guò) 16 毫秒,就容易出現(xiàn)掉幀的現(xiàn)象。
如何解決主線程長(zhǎng)時(shí)間被 JS 運(yùn)算?將JS運(yùn)算切割為多個(gè)步驟,分批完成。在完成一部分任務(wù)之后,將控制權(quán)交回給瀏覽器,讓瀏覽器有時(shí)間進(jìn)行頁(yè)面的渲染。等瀏覽器忙完之后,再繼續(xù)之前未完成的任務(wù)。
React 15 及以下版本通過(guò)遞歸的方式進(jìn)行渲染,使用的是 JS 引擎自身的函數(shù)調(diào)用棧,它會(huì)一直執(zhí)行到棧空為止。
而Fiber實(shí)現(xiàn)了自己的組件調(diào)用棧,它以鏈表的形式遍歷組件樹,可以靈活的暫停、繼續(xù)和丟棄執(zhí)行的任務(wù)。實(shí)現(xiàn)方式是使用了瀏覽器的requestIdleCallback
window.requestIdleCallback()會(huì)在瀏覽器空閑時(shí)期依次調(diào)用函數(shù),這就可以讓開發(fā)者在主事件循環(huán)中執(zhí)行后臺(tái)或低優(yōu)先級(jí)的任務(wù),而且不會(huì)對(duì)像動(dòng)畫和用戶交互這些延遲觸發(fā)但關(guān)鍵的事件產(chǎn)生影響。函數(shù)一般會(huì)按先進(jìn)先調(diào)用的順序執(zhí)行,除非函數(shù)在瀏覽器調(diào)用它之前就到了它的超時(shí)時(shí)間。
React 框架內(nèi)部的運(yùn)作可以分為 3 層:
Virtual DOM 層,描述頁(yè)面長(zhǎng)什么樣。
Reconciler 層,負(fù)責(zé)調(diào)用組件生命周期方法,進(jìn)行 Diff 運(yùn)算等。
Renderer 層,根據(jù)不同的平臺(tái),渲染出相應(yīng)的頁(yè)面,比較常見的是 ReactDOM 和 ReactNative。
Fiber 表征reconciliation階段所能拆分的最小工作單元,其實(shí)指的是一種鏈表樹,它可以用一個(gè)純 JS 對(duì)象來(lái)表示:
const fiber = { stateNode: {}, // 節(jié)點(diǎn)實(shí)例 child: {}, // 子節(jié)點(diǎn) sibling: {}, // 兄弟節(jié)點(diǎn) return: {}, // 表示處理完成后返回結(jié)果所要合并的目標(biāo),通常指向父節(jié)點(diǎn) };
Reconciler區(qū)別
以前的 Reconciler 被命名為Stack Reconciler。Stack Reconciler 運(yùn)作的過(guò)程是不能被打斷的,必須一條道走到黑;
Fiber Reconciler 每執(zhí)行一段時(shí)間,都會(huì)將控制權(quán)交回給瀏覽器,可以分段執(zhí)行;
從Stack Reconciler到Fiber Reconciler,源碼層面其實(shí)就是干了一件遞歸改循環(huán)的事情。
scheduling(調(diào)度)
scheduling(調(diào)度)是fiber reconciliation的一個(gè)過(guò)程,主要是進(jìn)行任務(wù)分配,達(dá)到分段執(zhí)行。任務(wù)的優(yōu)先級(jí)有六種:
synchronous,與之前的Stack Reconciler操作一樣,同步執(zhí)行
task,在next tick之前執(zhí)行
animation,下一幀之前執(zhí)行
high,在不久的將來(lái)立即執(zhí)行
low,稍微延遲執(zhí)行也沒(méi)關(guān)系
offscreen,下一次render時(shí)或scroll時(shí)才執(zhí)行
優(yōu)先級(jí)高的任務(wù)(如鍵盤輸入)可以打斷優(yōu)先級(jí)低的任務(wù)(如Diff)的執(zhí)行,從而更快的生效。
Fiber Reconciler 在執(zhí)行過(guò)程中,會(huì)分為 2 個(gè)階段:
階段一,生成 Fiber 樹,得出需要更新的節(jié)點(diǎn)信息。這一步是一個(gè)漸進(jìn)的過(guò)程,可以被打斷。
階段二,將需要更新的節(jié)點(diǎn)一次過(guò)批量更新,這個(gè)過(guò)程不能被打斷。
階段一可被打斷的特性,讓優(yōu)先級(jí)更高的任務(wù)先執(zhí)行,從框架層面大大降低了頁(yè)面掉幀的概率。
參考:
React Fiber 原理介紹
React Fiber
HOC 與render props區(qū)別
Render Props: 把將要包裹的組件作為props屬性傳入,然后容器組件調(diào)用這個(gè)屬性,并向其傳參。
實(shí)現(xiàn)方式:
1.通過(guò)props.children(props),props.children返回的是UI元素。 JSX 標(biāo)簽中的所有內(nèi)容都會(huì)作為一個(gè) children prop 傳遞給 RenderProps組件。因?yàn)?RenderProps 將 {props.children} 渲染在一個(gè)
中,被傳遞的這些子組件最終都會(huì)出現(xiàn)在輸出結(jié)果中。
// 定義 const RenderProps = props => <div> {props.children(props)} </div> // 調(diào)用 <RenderProps> {() => <>Hello RenderProps</>} </RenderProps>
2.通過(guò)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)點(diǎn):
1、支持ES6
2、不用擔(dān)心props命名問(wèn)題,在render函數(shù)中只取需要的state
3、不會(huì)產(chǎn)生無(wú)用的組件加深層級(jí)
4、render props模式的構(gòu)建都是動(dòng)態(tài)的,所有的改變都在render中觸發(fā),可以更好的利用組件內(nèi)的生命周期。
HOC: 接受一個(gè)組件作為參數(shù),返回一個(gè)新的組件的函數(shù)。
class Home extends React.Component { // UI } export default Connect()(Home);
高階組件由于每次都會(huì)返回一個(gè)新的組件,對(duì)于react來(lái)說(shuō),這是不利于diff和狀態(tài)復(fù)用的,所以高階組件的包裝不能在render 方法中進(jìn)行,而只能像上面那樣在組件聲明時(shí)包裹,這樣也就不利于動(dòng)態(tài)傳參。
優(yōu)點(diǎn):
1、支持ES6
2、復(fù)用性強(qiáng),HOC為純函數(shù)且返回值為組件,可以多層嵌套
3、支持傳入多個(gè)參數(shù),增強(qiáng)了適用范圍
缺點(diǎn):
1、當(dāng)多個(gè)HOC一起使用時(shí),無(wú)法直接判斷子組件的props是哪個(gè)HOC負(fù)責(zé)傳遞的
2、多個(gè)組件嵌套,容易產(chǎn)生同樣名稱的props
3、HOC可能會(huì)產(chǎn)生許多無(wú)用的組件,加深了組件的層級(jí)
總的來(lái)說(shuō),render props其實(shí)和高階組件類似,就是在puru component上增加state,響應(yīng)react的生命周期。
React 通信
react的數(shù)據(jù)流是單向的,最常見的就是通過(guò)props由父組件向子組件傳值。
父向子通信:傳入props
子向父通信:父組件向子組件傳一個(gè)函數(shù),然后通過(guò)這個(gè)函數(shù)的回調(diào),拿到子組件傳過(guò)來(lái)的值
父向?qū)O通信:利用context傳值。React.createContext()
兄弟間通信:
1、找一個(gè)相同的父組件,既可以用props傳遞數(shù)據(jù),也可以用context的方式來(lái)傳遞數(shù)據(jù)。
2、用一些全局機(jī)制去實(shí)現(xiàn)通信,比如redux等
3、發(fā)布訂閱模式
react合成事件
React 合成事件(SyntheticEvent)是 React 模擬原生 DOM 事件所有能力的一個(gè)事件對(duì)象,即瀏覽器原生事件的跨瀏覽器包裝器。
為什么要使用合成事件?
1.進(jìn)行瀏覽器兼容,實(shí)現(xiàn)更好的跨平臺(tái)
React 采用的是頂層事件代理機(jī)制,能夠保證冒泡一致性,可以跨瀏覽器執(zhí)行。React 提供的合成事件用來(lái)抹平不同瀏覽器事件對(duì)象之間的差異,將不同平臺(tái)事件模擬合成事件。
2.避免垃圾回收
事件對(duì)象可能會(huì)被頻繁創(chuàng)建和回收,因此 React 引入事件池,在事件池中獲取或釋放事件對(duì)象。即 React 事件對(duì)象不會(huì)被釋放掉,而是存放進(jìn)一個(gè)數(shù)組中,當(dāng)事件觸發(fā),就從這個(gè)數(shù)組中彈出,避免頻繁地去創(chuàng)建和銷毀(垃圾回收)。
3.方便事件統(tǒng)一管理和事務(wù)機(jī)制
實(shí)現(xiàn)原理
在 React 中,“合成事件”會(huì)以事件委托方式綁定在 document 對(duì)象上,并在組件卸載(unmount)階段自動(dòng)銷毀綁定的事件。
合成事件和原生事件
當(dāng)真實(shí) DOM 元素觸發(fā)事件,會(huì)冒泡到 document 對(duì)象后,再處理 React 事件;所以會(huì)先執(zhí)行原生事件,然后處理 React 事件;最后真正執(zhí)行 document 上掛載的事件。
合成事件和原生事件最好不要混用。原生事件中如果執(zhí)行了stopPropagation方法,則會(huì)導(dǎo)致其他React事件失效。因?yàn)樗性氐氖录o(wú)法冒泡到document上,所有的 React 事件都將無(wú)法被注冊(cè)。
合成事件的事件池
合成事件對(duì)象池,是 React 事件系統(tǒng)提供的一種性能優(yōu)化方式。合成事件對(duì)象在事件池統(tǒng)一管理,不同類型的合成事件具有不同的事件池。
react 虛擬dom
什么是虛擬dom?
在 React 中,render 執(zhí)行的結(jié)果得到的并不是真正的 DOM 節(jié)點(diǎn),而是輕量級(jí)的 JavaScript 對(duì)象,我們稱之為 virtual DOM。它通過(guò)JS的Object對(duì)象模擬DOM中的節(jié)點(diǎn),然后再通過(guò)特定的render方法將其渲染成真實(shí)的DOM節(jié)點(diǎn)。
虛擬 DOM 是 React 的一大亮點(diǎn),具有batching(批處理) 和高效的 Diff 算法。batching 把所有的 DOM 操作搜集起來(lái),一次性提交給真實(shí)的 DOM。diff 算法時(shí)間復(fù)雜度也從標(biāo)準(zhǔn)的的 Diff 算法的 O(n^3) 降到了 O(n)。
batching(批處理)
主要思想是,無(wú)論setState您在React事件處理程序或同步生命周期方法中進(jìn)行多少次調(diào)用,它都將被批處理成一個(gè)更新, 最終只有一次重新渲染。
虛擬 DOM 與 原生 DOM
如果沒(méi)有 Virtual DOM,就需要直接操作原生 DOM。在一個(gè)大型列表所有數(shù)據(jù)都變了的情況下,直接重置 innerHTML還算合理,但是,只有一行數(shù)據(jù)發(fā)生變化時(shí),它也需要重置整個(gè) innerHTML,這就造成了大量浪費(fèi)。
innerHTML 和 Virtual DOM 的重繪性能消耗:
innerHTML: render html string + 重新創(chuàng)建所有 DOM 元素
Virtual DOM: render Virtual DOM + diff + 必要的 DOM 更新
Virtual DOM render + diff 顯然比渲染 html 字符串要慢,但是它依然是純 js 層面的計(jì)算,比起后面的 DOM 操作來(lái)說(shuō),依然便宜了太多。innerHTML 的總計(jì)算量不管是 js 計(jì)算還是 DOM 操作都是和整個(gè)界面的大小相關(guān),但 Virtual DOM 的計(jì)算量只有 js 計(jì)算和界面大小相關(guān),DOM 操作是和數(shù)據(jù)的變動(dòng)量相關(guān)。
虛擬 DOM 與 MVVM
相比起 React,其他 MVVM 系框架比如 Angular, Knockout , Vue ,Avalon 采用的都是數(shù)據(jù)綁定。通過(guò) Directive/Binding 對(duì)象,觀察數(shù)據(jù)變化并保留對(duì)實(shí)際 DOM 元素的引用,當(dāng)有數(shù)據(jù)變化時(shí)進(jìn)行對(duì)應(yīng)的操作。MVVM 的變化檢查是數(shù)據(jù)層面的,而 React 的檢查是 DOM 結(jié)構(gòu)層面的。
MVVM 的性能也根據(jù)變動(dòng)檢測(cè)的實(shí)現(xiàn)原理有所不同:Angular 依賴于臟檢查;Knockout/Vue/Avalon 采用了依賴收集。
臟檢查:scope digest(watcher count) ) + 必要 DOM 更新
依賴收集:重新收集依賴(data change) ) + 必要 DOM 更新
Angular 最不效率的地方在于任何小變動(dòng)都有的和 watcher 數(shù)量相關(guān)的性能代價(jià),當(dāng)所有數(shù)據(jù)都變了的時(shí)候,Angular更有效。依賴收集在初始化和數(shù)據(jù)變化的時(shí)候都需要重新收集依賴,這個(gè)代價(jià)在小量更新的時(shí)候幾乎可以忽略,但在數(shù)據(jù)量龐大的時(shí)候也會(huì)產(chǎn)生一定的消耗。
性能比較
在比較性能的時(shí)候,要分清楚初始渲染、小量數(shù)據(jù)更新、大量數(shù)據(jù)更新這些不同的場(chǎng)合。Virtual DOM、臟檢查 MVVM、數(shù)據(jù)收集 MVVM 在不同場(chǎng)合各有不同的表現(xiàn)和不同的優(yōu)化需求。
Virtual DOM 為了提升小量數(shù)據(jù)更新時(shí)的性能,也需要針對(duì)性的優(yōu)化,比如 shouldComponentUpdate 或是 immutable data。
初始渲染:Virtual DOM > 臟檢查 >= 依賴收集
小量數(shù)據(jù)更新:依賴收集 >> Virtual DOM + 優(yōu)化 > 臟檢查(無(wú)法優(yōu)化)> Virtual DOM 無(wú)優(yōu)化
大量數(shù)據(jù)更新:臟檢查 + 優(yōu)化 >= 依賴收集 + 優(yōu)化 > Virtual DOM(無(wú)法/無(wú)需優(yōu)化)>> MVVM 無(wú)優(yōu)化
diff 算法
傳統(tǒng) diff 算法通過(guò)循環(huán)遞歸對(duì)節(jié)點(diǎn)進(jìn)行依次對(duì)比,算法復(fù)雜度達(dá)到 O(n^3),其中 n 是樹中節(jié)點(diǎn)的總數(shù)。O(n^3) 意味著如果要展示1000個(gè)節(jié)點(diǎn),就要依次執(zhí)行上十億次的比較, 這是無(wú)法滿足現(xiàn)代前端性能要求的。
diff 算法主要包括幾個(gè)步驟:
用 JS 對(duì)象的方式來(lái)表示 DOM 樹的結(jié)構(gòu),然后根據(jù)這個(gè)對(duì)象構(gòu)建出真實(shí)的 DOM 樹,插到文檔中。
當(dāng)狀態(tài)變更的時(shí)候,重新構(gòu)造一棵新的對(duì)象樹。然后用新的樹和舊的樹進(jìn)行比較,記錄兩棵樹的差異, 最后把所記錄的差異應(yīng)用到所構(gòu)建的真正的DOM樹上,視圖更新。
diff 策略
React 通過(guò)制定大膽的diff策略,將diff算法復(fù)雜度從 O(n^3) 轉(zhuǎn)換成 O(n) 。
React 通過(guò)分層求異的策略,對(duì) tree diff 進(jìn)行算法優(yōu)化;
React 通過(guò)相同類生成相似樹形結(jié)構(gòu),不同類生成不同樹形結(jié)構(gòu)的策略,對(duì) component diff 進(jìn)行算法優(yōu)化;
React 通過(guò)設(shè)置唯一 key的策略,對(duì) element diff 進(jìn)行算法優(yōu)化;
tree diff(層級(jí)比較)
React 對(duì)樹進(jìn)行分層比較,兩棵樹只會(huì)對(duì)同一層次的節(jié)點(diǎn)進(jìn)行比較。
當(dāng)發(fā)現(xiàn)節(jié)點(diǎn)已經(jīng)不存在,則該節(jié)點(diǎn)及其子節(jié)點(diǎn)會(huì)被完全刪除掉,不會(huì)進(jìn)行進(jìn)一步的比較。
這樣只需要對(duì)樹進(jìn)行一次遍歷,便能完成整個(gè) DOM 樹的比較。
當(dāng)出現(xiàn)節(jié)點(diǎn)跨層級(jí)移動(dòng)時(shí),并不會(huì)出現(xiàn)移動(dòng)操作,而是以該節(jié)點(diǎn)為根節(jié)點(diǎn)的樹被重新創(chuàng)建,這是一種影響 React 性能的操作,因此 React 官方建議不要進(jìn)行 DOM 節(jié)點(diǎn)跨層級(jí)的操作。
先進(jìn)行樹結(jié)構(gòu)的層級(jí)比較,對(duì)同一個(gè)父節(jié)點(diǎn)下的所有子節(jié)點(diǎn)進(jìn)行比較;
接著看節(jié)點(diǎn)是什么類型的,是組件就做 Component Diff;
如果節(jié)點(diǎn)是標(biāo)簽或者元素,就做 Element Diff;
注意:在開發(fā)組件時(shí),保持穩(wěn)定的 DOM 結(jié)構(gòu)會(huì)有助于性能的提升。例如,可以通過(guò) CSS 隱藏或顯示節(jié)點(diǎn),而不是真的移除或添加 DOM 節(jié)點(diǎn)。
component diff(組件比較)
如果是同一類型的組件,按照原策略繼續(xù)比較 virtual DOM tree。
如果不是,則將該組件判斷為 dirty component,替換整個(gè)組件下的所有子節(jié)點(diǎn)。舉個(gè)例子,當(dāng)一個(gè)元素從 變成
對(duì)于同一類型的組件,有可能其 Virtual DOM 沒(méi)有任何變化,如果能夠確切的知道這點(diǎn)那可以節(jié)省大量的 diff 運(yùn)算時(shí)間。因此 React 允許用戶通過(guò) shouldComponentUpdate() 來(lái)判斷該組件是否需要進(jìn)行 diff。
對(duì)于兩個(gè)不同類型但結(jié)構(gòu)相似的組件,不會(huì)比較二者的結(jié)構(gòu),而且替換整個(gè)組件的所有內(nèi)容。不同類型的 component 是很少存在相似 DOM tree 的機(jī)會(huì),因此這種極端因素很難在實(shí)現(xiàn)開發(fā)過(guò)程中造成重大影響的。
element diff (元素比較)
當(dāng)節(jié)點(diǎn)處于同一層級(jí)時(shí),React diff 提供了三種節(jié)點(diǎn)操作,分別為:INSERT_MARKUP(插入)、MOVE_EXISTING(移動(dòng))和 REMOVE_NODE(刪除)。
INSERT_MARKUP,新的 component 類型不在老集合里, 即是全新的節(jié)點(diǎn),需要對(duì)新節(jié)點(diǎn)執(zhí)行插入操作。
MOVE_EXISTING,在老集合有新 component 類型,且 element 是可更新的類型,這種情況下需要做移動(dòng)操作,可以復(fù)用以前的 DOM 節(jié)點(diǎn)。
REMOVE_NODE,老 component 類型,在新集合里也有,但對(duì)應(yīng)的 element 不同則不能直接復(fù)用和更新,需要執(zhí)行刪除操作,或者老 component 不在新集合里的,也需要執(zhí)行刪除操作。
<ul> <li>Duke</li> <li>Villanova</li> </ul> <ul> <li>Connecticut</li> <li>Duke</li> <li>Villanova</li> </ul>
React 并不會(huì)意識(shí)到應(yīng)該保留<li>Duke</li>和<li>Villanova</li>,而是會(huì)重建每一個(gè)子元素,不會(huì)進(jìn)行移動(dòng) DOM 操作。
key 優(yōu)化
為了解決上述問(wèn)題,React 引入了 key 屬性, 對(duì)同一層級(jí)的同組子節(jié)點(diǎn),添加唯一 key 進(jìn)行區(qū)分。
當(dāng)子元素?fù)碛?key 時(shí),React 使用 key 來(lái)匹配原有樹上的子元素以及最新樹上的子元素。如果有相同的節(jié)點(diǎn),無(wú)需進(jìn)行節(jié)點(diǎn)刪除和創(chuàng)建,只需要將老集合中節(jié)點(diǎn)的位置進(jìn)行移動(dòng),更新為新集合中節(jié)點(diǎn)的位置。
在開發(fā)過(guò)程中,盡量減少類似將最后一個(gè)節(jié)點(diǎn)移動(dòng)到列表首部的操作,當(dāng)節(jié)點(diǎn)數(shù)量過(guò)大或更新操作過(guò)于頻繁時(shí),在一定程度上會(huì)影響 React 的渲染性能。
key 不需要全局唯一,但在列表中需要保持唯一。
Key 應(yīng)該具有穩(wěn)定,可預(yù)測(cè),以及列表內(nèi)唯一的特質(zhì)。不穩(wěn)定的 key(比如通過(guò) Math.random() 生成的)會(huì)導(dǎo)致許多組件實(shí)例和 DOM 節(jié)點(diǎn)被不必要地重新創(chuàng)建,這可能導(dǎo)致性能下降和子組件中的狀態(tài)丟失。
react與vue區(qū)別
1. 監(jiān)聽數(shù)據(jù)變化的實(shí)現(xiàn)原理不同
Vue通過(guò) getter/setter以及一些函數(shù)的劫持,能精確知道數(shù)據(jù)變化。
React默認(rèn)是通過(guò)比較引用的方式(diff)進(jìn)行的,如果不優(yōu)化可能導(dǎo)致大量不必要的VDOM的重新渲染。
2. 數(shù)據(jù)流不同
Vue1.0中可以實(shí)現(xiàn)兩種雙向綁定:父子組件之間props可以雙向綁定;組件與DOM之間可以通過(guò)v-model雙向綁定。
Vue2.x中父子組件之間不能雙向綁定了(但是提供了一個(gè)語(yǔ)法糖自動(dòng)幫你通過(guò)事件的方式修改)。
React一直不支持雙向綁定,提倡的是單向數(shù)據(jù)流,稱之為onChange/setState()模式。
3. HoC和mixins
Vue組合不同功能的方式是通過(guò)mixin,Vue中組件是一個(gè)被包裝的函數(shù),并不簡(jiǎn)單的就是我們定義組件的時(shí)候傳入的對(duì)象或者函數(shù)。
React組合不同功能的方式是通過(guò)HoC(高階組件)。
4. 模板渲染方式的不同
模板的語(yǔ)法不同,React是通過(guò)JSX渲染模板, Vue是通過(guò)一種拓展的HTML語(yǔ)法進(jìn)行渲染。
模板的原理不同,React通過(guò)原生JS實(shí)現(xiàn)模板中的常見語(yǔ)法,比如插值,條件,循環(huán)等。而Vue是在和組件JS代碼分離的單獨(dú)的模板中,通過(guò)指令來(lái)實(shí)現(xiàn)的,比如 v-if 。
舉個(gè)例子,說(shuō)明React的好處:react中render函數(shù)是支持閉包特性的,所以我們import的組件在render中可以直接調(diào)用。但是在Vue中,由于模板中使用的數(shù)據(jù)都必須掛在 this 上進(jìn)行一次中轉(zhuǎn),所以我們import 一個(gè)組件完了之后,還需要在 components 中再聲明下。
5. 渲染過(guò)程不同
Vue可以更快地計(jì)算出Virtual DOM的差異,這是由于它會(huì)跟蹤每一個(gè)組件的依賴關(guān)系,不需要重新渲染整個(gè)組件樹。
React當(dāng)狀態(tài)被改變時(shí),全部子組件都會(huì)重新渲染。通過(guò)shouldComponentUpdate這個(gè)生命周期方法可以進(jìn)行控制,但Vue將此視為默認(rèn)的優(yōu)化。
6. 框架本質(zhì)不同
Vue本質(zhì)是MVVM框架,由MVC發(fā)展而來(lái);
React是前端組件化框架,由后端組件化發(fā)展而來(lái)。
性能優(yōu)化
1. 靜態(tài)資源使用 cdn
CDN是一組分布在多個(gè)不同地理位置的 Web 服務(wù)器。當(dāng)服務(wù)器離用戶越遠(yuǎn)時(shí),延遲越高。
2. 無(wú)阻塞
頭部?jī)?nèi)聯(lián)的樣式和腳本會(huì)阻塞頁(yè)面的渲染,樣式放在頭部并使用link方式引入,腳本放在尾部并使用異步方式加載
3. 壓縮文件
壓縮文件可以減少文件下載時(shí)間。
1.在 webpack 可以使用如下插件進(jìn)行壓縮:
JavaScript:UglifyPlugin
CSS :MiniCssExtractPlugin
HTML:HtmlWebpackPlugin
2.使用 gzip 壓縮。通過(guò)向 HTTP 請(qǐng)求頭中的 Accept-Encoding 頭添加 gzip 標(biāo)識(shí)來(lái)開啟這一功能。
4. 圖片優(yōu)化
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
圖片懶加載
響應(yīng)式圖片:瀏覽器根據(jù)屏幕大小自動(dòng)加載合適的圖片。
降低圖片質(zhì)量:方法有兩種,一是通過(guò) webpack 插件 image-webpack-loader,二是通過(guò)在線網(wǎng)站進(jìn)行壓縮。
5. 減少重繪重排
降低 CSS 選擇器的復(fù)雜性
使用 transform 和 opacity 屬性更改來(lái)實(shí)現(xiàn)動(dòng)畫
用 JavaScript 修改樣式時(shí),最好不要直接寫樣式,而是替換 class 來(lái)改變樣式。
如果要對(duì) DOM 元素執(zhí)行一系列操作,可以將 DOM 元素脫離文檔流,修改完成后,再將它帶回文檔。推薦使用隱藏元素(display:none)或文檔碎片(DocumentFragement),都能很好的實(shí)現(xiàn)這個(gè)方案。
6. 使用 requestAnimationFrame 來(lái)實(shí)現(xiàn)視覺(jué)變化
window.requestAnimationFrame() 告訴瀏覽器——你希望執(zhí)行一個(gè)動(dòng)畫,并且要求瀏覽器在下次重繪之前調(diào)用指定的回調(diào)函數(shù)更新動(dòng)畫。該方法需要傳入一個(gè)回調(diào)函數(shù)作為參數(shù),該回調(diào)函數(shù)會(huì)在瀏覽器下一次重繪之前執(zhí)行
7. webpack 打包, 添加文件緩存
index.html 設(shè)置成 no-cache,這樣每次請(qǐng)求的時(shí)候都會(huì)比對(duì)一下 index.html 文件有沒(méi)變化,如果沒(méi)變化就使用緩存,有變化就使用新的 index.html 文件。
其他所有文件一律使用長(zhǎng)緩存,例如設(shè)置成緩存一年 maxAge: 1000 * 60 * 60 * 24 * 365。
前端代碼使用 webpack 打包,根據(jù)文件內(nèi)容生成對(duì)應(yīng)的文件名,每次重新打包時(shí)只有內(nèi)容發(fā)生了變化,文件名才會(huì)發(fā)生變化。
max-age: 設(shè)置緩存存儲(chǔ)的最大周期,超過(guò)這個(gè)時(shí)間緩存被認(rèn)為過(guò)期(單位秒)。在這個(gè)時(shí)間前,瀏覽器讀取文件不會(huì)發(fā)出新請(qǐng)求,而是直接使用緩存。
指定 no-cache 表示客戶端可以緩存資源,每次使用緩存資源前都必須重新驗(yàn)證其有效性
輸入url后發(fā)生了什么
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
DNS域名解析;
建立TCP連接(三次握手);
發(fā)送HTTP請(qǐng)求;
服務(wù)器處理請(qǐng)求;
返回響應(yīng)結(jié)果;
關(guān)閉TCP連接(四次握手);
瀏覽器解析HTML;
瀏覽器布局渲染;
1. DNS域名解析:拿到服務(wù)器ip
客戶端收到你輸入的域名地址后,它首先去找本地的hosts文件,檢查在該文件中是否有相應(yīng)的域名、IP對(duì)應(yīng)關(guān)系,如果有,則向其IP地址發(fā)送請(qǐng)求,如果沒(méi)有,再去找DNS服務(wù)器。
2. 建立TCP鏈接:客戶端鏈接服務(wù)器
TCP提供了一種可靠、面向連接、字節(jié)流、傳輸層的服務(wù)。對(duì)于客戶端與服務(wù)器的TCP鏈接,必然要說(shuō)的就是『三次握手』。“3次握手”的作用就是雙方都能明確自己和對(duì)方的收、發(fā)能力是正常的。
客戶端發(fā)送一個(gè)帶有SYN標(biāo)志的數(shù)據(jù)包給服務(wù)端,服務(wù)端收到后,回傳一個(gè)帶有SYN/ACK標(biāo)志的數(shù)據(jù)包以示傳達(dá)確認(rèn)信息,最后客戶端再回傳一個(gè)帶ACK標(biāo)志的數(shù)據(jù)包,代表握手結(jié)束,連接成功。
SYN —— 用于初如化一個(gè)連接的序列號(hào)。
ACK —— 確認(rèn),使得確認(rèn)號(hào)有效。
RST —— 重置連接。
FIN —— 該報(bào)文段的發(fā)送方已經(jīng)結(jié)束向?qū)Ψ桨l(fā)送數(shù)據(jù)。
客戶端:“你好,在家不。” -- SYN
服務(wù)端:“在的,你來(lái)吧。” -- SYN + ACK
客戶端:“好嘞。” -- ACK
3. 發(fā)送HTTP請(qǐng)求
4. 服務(wù)器處理請(qǐng)求
5. 返回響應(yīng)結(jié)果
6. 關(guān)閉TCP連接(需要4次握手)
為了避免服務(wù)器與客戶端雙方的資源占用和損耗,當(dāng)雙方?jīng)]有請(qǐng)求或響應(yīng)傳遞時(shí),任意一方都可以發(fā)起關(guān)閉請(qǐng)求。
關(guān)閉連接時(shí),服務(wù)器收到對(duì)方的FIN報(bào)文時(shí),僅僅表示客戶端不再發(fā)送數(shù)據(jù)了但是還能接收數(shù)據(jù),而服務(wù)器也未必全部數(shù)據(jù)都發(fā)送給客戶端,所以服務(wù)器可以立即關(guān)閉,也可以發(fā)送一些數(shù)據(jù)給對(duì)方后,再發(fā)送FIN報(bào)文給對(duì)方來(lái)表示同意現(xiàn)在關(guān)閉連接,因此,己方ACK和FIN一般都會(huì)分開發(fā)送,從而導(dǎo)致多了一次。
客戶端:“兄弟,我這邊沒(méi)數(shù)據(jù)要傳了,咱關(guān)閉連接吧。” -- FIN + seq
服務(wù)端:“收到,我看看我這邊有木有數(shù)據(jù)了。” -- ACK + seq + ack
服務(wù)端:“兄弟,我這邊也沒(méi)數(shù)據(jù)要傳你了,咱可以關(guān)閉連接了。” - FIN + ACK + seq + ack
客戶端:“好嘞。” -- ACK + seq + ack
7. 瀏覽器解析HTML
瀏覽器需要加載解析的不僅僅是HTML,還包括CSS、JS,以及還要加載圖片、視頻等其他媒體資源。
瀏覽器通過(guò)解析HTML,生成DOM樹,解析CSS,生成CSSOM樹,然后通過(guò)DOM樹和CSSPOM樹生成渲染樹。渲染樹與DOM樹不同,渲染樹中并沒(méi)有head、display為none等不必顯示的節(jié)點(diǎn)。
瀏覽器的解析過(guò)程并非是串連進(jìn)行的,比如在解析CSS的同時(shí),可以繼續(xù)加載解析HTML,但在解析執(zhí)行JS腳本時(shí),會(huì)停止解析后續(xù)HTML,會(huì)出現(xiàn)阻塞問(wèn)題。
8. 瀏覽器渲染頁(yè)面
根據(jù)渲染樹布局,計(jì)算CSS樣式,即每個(gè)節(jié)點(diǎn)在頁(yè)面中的大小和位置等幾何信息。HTML默認(rèn)是流式布局的,CSS和js會(huì)打破這種布局,改變DOM的外觀樣式以及大小和位置。最后瀏覽器繪制各個(gè)節(jié)點(diǎn),將頁(yè)面展示給用戶。
replaint:屏幕的一部分重畫,不影響整體布局,比如某個(gè)CSS的背景色變了,但元素的幾何尺寸和位置不變。
reflow:意味著元素的幾何尺寸變了,需要重新計(jì)算渲染樹。
參考:
細(xì)說(shuō)瀏覽器輸入U(xiǎn)RL后發(fā)生了什么
瀏覽器輸入 URL 后發(fā)生了什么?
前端路由
什么是路由
路由是用來(lái)跟后端服務(wù)器進(jìn)行交互的一種方式,通過(guò)不同的路徑請(qǐng)求不同的資源。
路由這概念最開始是在后端出現(xiàn), 在前后端不分離的時(shí)期, 由后端來(lái)控制路由, 服務(wù)器接收客戶端的請(qǐng)求,解析對(duì)應(yīng)的url路徑, 并返回對(duì)應(yīng)的頁(yè)面/資源。
前端路由
Ajax,全稱 Asynchronous JavaScript And XML,是瀏覽器用來(lái)實(shí)現(xiàn)異步加載的一種技術(shù)方案。
在Ajax沒(méi)有出現(xiàn)時(shí)期,大多數(shù)的網(wǎng)頁(yè)都是通過(guò)直接返回 HTML,用戶的每次更新操作都需要重新刷新頁(yè)面,及其影響交互體驗(yàn)。為了解決這個(gè)問(wèn)題,提出了Ajax(異步加載方案), 有了 Ajax 后,用戶交互就不用每次都刷新頁(yè)面。后來(lái)出現(xiàn)SPA單頁(yè)應(yīng)用。
SPA 中用戶的交互是通過(guò) JS 改變 HTML 內(nèi)容來(lái)實(shí)現(xiàn)的,頁(yè)面本身的 url 并沒(méi)有變化,這導(dǎo)致了兩個(gè)問(wèn)題:
SPA 無(wú)法記住用戶的操作記錄,無(wú)論是刷新、前進(jìn)還是后退,都無(wú)法展示用戶真實(shí)的期望內(nèi)容。
SPA 中雖然由于業(yè)務(wù)的不同會(huì)有多種頁(yè)面展示形式,但只有一個(gè) url,對(duì) SEO 不友好,不方便搜索引擎進(jìn)行收錄。
前端路由就是為了解決上述問(wèn)題而出現(xiàn)的。
前端路由的實(shí)現(xiàn)方式
前端路由的實(shí)現(xiàn)實(shí)際上是檢測(cè) url 的變化,截獲 url 地址,解析來(lái)匹配路由規(guī)則。有下面兩種實(shí)現(xiàn)方式:
1. Hash模式
hash 就是指 url 后的 # 號(hào)以及后面的字符。 #后面 hash 值的變化,并不會(huì)導(dǎo)致瀏覽器向服務(wù)器發(fā)出請(qǐng)求,瀏覽器不發(fā)請(qǐng)求,也就不會(huì)刷新頁(yè)面。
hash 的改變會(huì)觸發(fā) hashchange 事件,可以用onhashchange事件來(lái)監(jiān)聽hash值的改變。
// 監(jiān)聽hash變化,點(diǎn)擊瀏覽器的前進(jìn)后退會(huì)觸發(fā) window.onhashchange = function() { ... } window.addEventListener('hashchange', function(event) { ...}, false);
2.History 模式
在 HTML5 之前,瀏覽器就已經(jīng)有了 history 對(duì)象。但在早期的 history 中只能用于多頁(yè)面的跳轉(zhuǎn):
history.go(-1); // 后退一頁(yè) history.go(2); // 前進(jìn)兩頁(yè) history.forward(); // 前進(jìn)一頁(yè) history.back(); // 后退一頁(yè)
在 HTML5 的規(guī)范中,history 新增了幾個(gè) API:
history.pushState(); // 向當(dāng)前瀏覽器會(huì)話的歷史堆棧中添加一個(gè)狀態(tài) history.replaceState();// 修改了當(dāng)前的歷史記錄項(xiàng)(不是新建一個(gè)) history.state // 返回一個(gè)表示歷史堆棧頂部的狀態(tài)的值
由于 history.pushState() 和 history.replaceState() 可以改變 url 同時(shí),不會(huì)刷新頁(yè)面,所以在 HTML5 中的 histroy 具備了實(shí)現(xiàn)前端路由的能力。
window對(duì)象提供了onpopstate事件來(lái)監(jiān)聽歷史棧的改變,一旦歷史棧信息發(fā)生改變, 便會(huì)觸發(fā)該事件。
調(diào)用history.pushState()或history.replaceState()不會(huì)觸發(fā)popstate事件。只有在做出瀏覽器動(dòng)作時(shí),才會(huì)觸發(fā)該事件,例如執(zhí)行history.back()或history.forward()后觸發(fā) window.onpopstate事件。
// 歷史棧改變 window.onpopstate = function() { ... }
注意:pushState() 不會(huì)造成 hashchange 事件調(diào)用, 即使新的URL和之前的URL只是錨的數(shù)據(jù)不同。
兩種模式對(duì)比
前端路由實(shí)踐
vue-router/react-router 都是基于前端路由的原理實(shí)現(xiàn)的~
react-router常用的 history 有三種形式:
browserHistory: 使用瀏覽器中的History API 用于處理 URL。history 在 DOM 上的實(shí)現(xiàn),用于支持 HTML5 history API 的瀏覽器。
hashHistory: 使用 URL 中的 hash(#)部分去創(chuàng)建路由。history 在 DOM 上的實(shí)現(xiàn),用于舊版瀏覽器。
createMemoryHistory: 不會(huì)在地址欄被操作或讀取,history 在內(nèi)存上的實(shí)現(xiàn),用于測(cè)試或非 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
實(shí)現(xiàn)Babel代碼轉(zhuǎn)換功能的核心,就是Babel插件(plugin)。Babel插件一般盡可能拆成小的力度,開發(fā)者可以按需引進(jìn), 既提高了性能,也提高了擴(kuò)展性。比如對(duì)ES6轉(zhuǎn)ES5的功能,Babel官方拆成了20+個(gè)插件。開發(fā)者想要體驗(yàn)ES6的箭頭函數(shù)特性,那只需要引入transform-es2015-arrow-functions插件就可以,而不是加載ES6全家桶。
Preset
可以簡(jiǎn)單的把Babel Preset視為Babel Plugin的集合。想要將所有ES6的代碼轉(zhuǎn)成ES5,逐個(gè)插件引入的效率比較低下, 就可以采用Babel Preset。比如babel-preset-es2015就包含了所有跟ES6轉(zhuǎn)換有關(guān)的插件。
Plugin與Preset執(zhí)行順序
可以同時(shí)使用多個(gè)Plugin和Preset,此時(shí),它們的執(zhí)行順序非常重要。
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
先執(zhí)行完所有Plugin,再執(zhí)行Preset。
多個(gè)Plugin,按照聲明次序順序執(zhí)行。
多個(gè)Preset,按照聲明次序逆序執(zhí)行。
比如.babelrc配置如下,那么執(zhí)行的順序?yàn)椋?/p>
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
Plugin:transform-react-jsx、transform-async-to-generator
Preset:es2016、es2015
{ "presets": [ "es2015", "es2016" ], "plugins": [ "transform-react-jsx", "transform-async-to-generator" ] }
怎樣開發(fā)和部署前端代碼
為了進(jìn)一步提升網(wǎng)站性能,會(huì)把靜態(tài)資源和動(dòng)態(tài)網(wǎng)頁(yè)分集群部署,靜態(tài)資源會(huì)被部署到CDN節(jié)點(diǎn)上,網(wǎng)頁(yè)中引用的資源也會(huì)變成對(duì)應(yīng)的部署路徑。當(dāng)需要更新靜態(tài)資源的時(shí)候,同時(shí)也會(huì)更新html中的引用。
如果同時(shí)改了頁(yè)面結(jié)構(gòu)和樣式,也更新了靜態(tài)資源對(duì)應(yīng)的url地址,現(xiàn)在要發(fā)布代碼上線,是先上線頁(yè)面,還是先上線靜態(tài)資源?
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
先部署頁(yè)面,再部署資源:在二者部署的時(shí)間間隔內(nèi),如果有用戶訪問(wèn)頁(yè)面,就會(huì)在新的頁(yè)面結(jié)構(gòu)中加載舊的資源,并且把這個(gè)舊版本的資源當(dāng)做新版本緩存起來(lái),其結(jié)果就是:用戶訪問(wèn)到了一個(gè)樣式錯(cuò)亂的頁(yè)面,除非手動(dòng)刷新,否則在資源緩存過(guò)期之前,頁(yè)面會(huì)一直執(zhí)行錯(cuò)誤。
先部署資源,再部署頁(yè)面:在部署時(shí)間間隔之內(nèi),有舊版本資源本地緩存的用戶訪問(wèn)網(wǎng)站,由于請(qǐng)求的頁(yè)面是舊版本的,資源引用沒(méi)有改變,瀏覽器將直接使用本地緩存,這種情況下頁(yè)面展現(xiàn)正常;但沒(méi)有本地緩存或者緩存過(guò)期的用戶訪問(wèn)網(wǎng)站,就會(huì)出現(xiàn)舊版本頁(yè)面加載新版本資源的情況,導(dǎo)致頁(yè)面執(zhí)行錯(cuò)誤,但當(dāng)頁(yè)面完成部署,這部分用戶再次訪問(wèn)頁(yè)面又會(huì)恢復(fù)正常了。
這個(gè)奇葩問(wèn)題,起源于資源的 覆蓋式發(fā)布,用 待發(fā)布資源 覆蓋 已發(fā)布資源,就有這種問(wèn)題。
解決它也好辦,就是實(shí)現(xiàn) 非覆蓋式發(fā)布。用文件的摘要信息來(lái)對(duì)資源文件進(jìn)行重命名,把摘要信息放到資源文件發(fā)布路徑中,這樣,內(nèi)容有修改的資源就變成了一個(gè)新的文件發(fā)布到線上,不會(huì)覆蓋已有的資源文件。
上線過(guò)程中,先全量部署靜態(tài)資源,再灰度部署頁(yè)面,整個(gè)問(wèn)題就比較完美的解決了。
大公司的靜態(tài)資源優(yōu)化方案,基本上要實(shí)現(xiàn)這么幾個(gè)東西:
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
配置超長(zhǎng)時(shí)間的本地緩存 —— 節(jié)省帶寬,提高性能
采用內(nèi)容摘要作為緩存更新依據(jù) —— 精確的緩存控制
靜態(tài)資源CDN部署 —— 優(yōu)化網(wǎng)絡(luò)請(qǐng)求
更改資源發(fā)布路徑實(shí)現(xiàn)非覆蓋式發(fā)布 —— 平滑升級(jí)
大數(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ǔ)知識(shí)整理匯總”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!
分享標(biāo)題:一些前端基礎(chǔ)知識(shí)整理匯總
標(biāo)題URL:http://chinadenli.net/article36/geossg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供自適應(yīng)網(wǎng)站、品牌網(wǎng)站制作、做網(wǎng)站、網(wǎng)站設(shè)計(jì)、用戶體驗(yàn)、靜態(tài)網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)