自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

前端基礎(chǔ)知識(shí)整理匯總(下)

開(kāi)發(fā) 前端
接上前面兩期的內(nèi)容,《前端基礎(chǔ)知識(shí)整理匯總(上)》、《前端基礎(chǔ)知識(shí)整理匯總(中)》,如果你還沒(méi)有看前面內(nèi)容的話,建議你可以點(diǎn)開(kāi)連接看看,也可以收藏著有空的時(shí)候,慢慢看。

[[384759]]

接上前面兩期的內(nèi)容,《前端基礎(chǔ)知識(shí)整理匯總(上)》、《前端基礎(chǔ)知識(shí)整理匯總(中)》,如果你還沒(méi)有看前面內(nèi)容的話,建議你可以點(diǎn)開(kāi)連接看看,也可以收藏著有空的時(shí)候,慢慢看。

以下是這個(gè)內(nèi)容的最后一篇內(nèi)容。

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)樽宇?lèi)自己的this對(duì)象,必須先通過(guò)父類(lèi)的構(gòu)造函數(shù)完成塑造,得到與父類(lèi)同樣的實(shí)例屬性和方法,然后再對(duì)其進(jìn)行加工,加上子類(lèi)自己的實(shí)例屬性和方法。如果不調(diào)用super方法,子類(lèi)就得不到this對(duì)象。

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

為什么super要傳 props?

把 props 傳進(jìn) super 是必要的,這使得基類(lèi) 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。

  1. // React 內(nèi)部 
  2. class Component { 
  3.   constructor(props) { 
  4.     this.props = props; // 初始化 this.props 
  5.     // ... 
  6.   } 
  7.  
  8. // React 內(nèi)部 
  9. const instance = new Button(props); 
  10. instance.props = props; // 給實(shí)例設(shè)置 props 
  11.  
  12. // Button類(lèi)組件 
  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前調(diào)用,且只會(huì)被調(diào)用一次。

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

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

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

當(dāng) render 被調(diào)用時(shí),它會(huì)檢查 this.props 和 this.state 的變化并返回以下類(lèi)型之一:

  • 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 子樹(shù)中。Portal 提供了一種將子節(jié)點(diǎn)渲染到存在于父組件以外的 DOM 節(jié)點(diǎn)的優(yōu)秀的方案。
  • 字符串或數(shù)值類(lèi)型。它們?cè)?DOM 中會(huì)被渲染為文本節(jié)點(diǎn)。
  • 布爾類(lèi)型或 null。什么都不渲染。

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

3. componentDidMount

會(huì)在組件掛載后(插入 DOM 樹(shù)中)立即調(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)編寫(xiě) 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è)階段:

  1. render phase:這個(gè)階段決定究竟哪些組件會(huì)被更新。
  2. commit phase:這個(gè)階段是 React 開(kāi)始執(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)限制開(kā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ò)誤之類(lèi)的情況。

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

  1. error —— 拋出的錯(cuò)誤。
  2. info —— 帶有 componentStack key 的對(duì)象

生命周期比較

16.0 前生命周期


16.0 后生命周期:


參考:

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

react 16做了哪些更新

  1. react作為一個(gè)ui庫(kù),將前端編程由傳統(tǒng)的命令式編程轉(zhuǎn)變?yōu)槁暶魇骄幊?,即所謂的數(shù)據(jù)驅(qū)動(dòng)視圖。如果直接更新真實(shí)dom,比如將生成的html直接采用innerHtml替換,會(huì)帶來(lái)重繪重排之類(lèi)的性能問(wèn)題。為了盡量提高性能,React團(tuán)隊(duì)引入了虛擬dom,即采用js對(duì)象來(lái)描述dom樹(shù),通過(guò)對(duì)比前后兩次的虛擬對(duì)象,來(lái)找到最小的dom操作(vdom diff),以此提高性能。
  2. 上面提到的reactDom diff,在react 16之前,這個(gè)過(guò)程我們稱(chēng)之為stack reconciler,它是一個(gè)遞歸的過(guò)程,在樹(shù)很深的時(shí)候,單次diff時(shí)間過(guò)長(zhǎng)會(huì)造成JS線程持續(xù)被占用,用戶交互響應(yīng)遲滯,頁(yè)面渲染會(huì)出現(xiàn)明顯的卡頓,這在現(xiàn)代前端是一個(gè)致命的問(wèn)題。
  3. 所以為了解決這種問(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è)面上。
  4. 由于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)開(kāi)始,為了更好的節(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以前只有類(lèi)組件有對(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的新鏈表。

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

state

其實(shí)state不是hooks獨(dú)有的,類(lèi)操作的setState也存在。

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

react 會(huì)生成一棵組件樹(shù)(或Fiber 單鏈表),樹(shù)中每個(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)用棧,它以鏈表的形式遍歷組件樹(shù),可以靈活的暫停、繼續(xù)和丟棄執(zhí)行的任務(wù)。實(shí)現(xiàn)方式是使用了瀏覽器的requestIdleCallback

  • window.requestIdleCallback()會(huì)在瀏覽器空閑時(shí)期依次調(diào)用函數(shù),這就可以讓開(kāi)發(fā)者在主事件循環(huán)中執(zhí)行后臺(tái)或低優(yōu)先級(jí)的任務(wù),而且不會(huì)對(duì)像動(dòng)畫(huà)和用戶交互這些延遲觸發(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è)面,比較常見(jiàn)的是 ReactDOM 和 ReactNative。

Fiber 表征reconciliation階段所能拆分的最小工作單元,其實(shí)指的是一種鏈表樹(shù),它可以用一個(gè)純 JS 對(duì)象來(lái)表示:

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

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ù)(如鍵盤(pán)輸入)可以打斷優(yōu)先級(jí)低的任務(wù)(如Diff)的執(zhí)行,從而更快的生效。

Fiber Reconciler 在執(zhí)行過(guò)程中,會(huì)分為 2 個(gè)階段:

  • 階段一,生成 Fiber 樹(shù),得出需要更新的節(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é)果中。

  1. // 定義 
  2. const RenderProps = props => <div> 
  3.    {props.children(props)} 
  4. </div> 
  5.  
  6. // 調(diào)用 
  7. <RenderProps> 
  8.     {() => <>Hello RenderProps</>} 
  9. </RenderProps> 

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

  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. // 調(diào)用 
  13. <LoginForm 
  14.   login={() => <h1>LOGIN</h1>} 
  15.   noLogin={() => <h1>NOT LOGIN</h1>} 
  16. /> 

 優(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ù)。

  1. class Home extends React.Component { 
  2.   // UI 
  3.  
  4. 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)生同樣名稱(chēng)的props

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

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

React 通信

react的數(shù)據(jù)流是單向的,最常見(jiàn)的就是通過(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)建和銷(xiāo)毀(垃圾回收)。

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

實(shí)現(xiàn)原理

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

合成事件和原生事件

當(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)一管理,不同類(lèi)型的合成事件具有不同的事件池。

react 虛擬dom

什么是虛擬dom?

在 React 中,render 執(zhí)行的結(jié)果得到的并不是真正的 DOM 節(jié)點(diǎn),而是輕量級(jí)的 JavaScript 對(duì)象,我們稱(chēng)之為 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 是樹(shù)中節(jié)點(diǎn)的總數(shù)。O(n^3) 意味著如果要展示1000個(gè)節(jié)點(diǎn),就要依次執(zhí)行上十億次的比較, 這是無(wú)法滿足現(xiàn)代前端性能要求的。

diff 算法主要包括幾個(gè)步驟:

  • 用 JS 對(duì)象的方式來(lái)表示 DOM 樹(shù)的結(jié)構(gòu),然后根據(jù)這個(gè)對(duì)象構(gòu)建出真實(shí)的 DOM 樹(shù),插到文檔中。
  • 當(dāng)狀態(tài)變更的時(shí)候,重新構(gòu)造一棵新的對(duì)象樹(shù)。然后用新的樹(shù)和舊的樹(shù)進(jìn)行比較,記錄兩棵樹(shù)的差異, 最后把所記錄的差異應(yīng)用到所構(gòu)建的真正的DOM樹(shù)上,視圖更新。

diff 策略

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

  • React 通過(guò)分層求異的策略,對(duì) tree diff 進(jìn)行算法優(yōu)化;
  • React 通過(guò)相同類(lèi)生成相似樹(shù)形結(jié)構(gòu),不同類(lèi)生成不同樹(shù)形結(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ì)樹(shù)進(jìn)行分層比較,兩棵樹(shù)只會(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ì)樹(shù)進(jìn)行一次遍歷,便能完成整個(gè) DOM 樹(shù)的比較。

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

  • 先進(jìn)行樹(shù)結(jié)構(gòu)的層級(jí)比較,對(duì)同一個(gè)父節(jié)點(diǎn)下的所有子節(jié)點(diǎn)進(jìn)行比較;
  • 接著看節(jié)點(diǎn)是什么類(lèi)型的,是組件就做 Component Diff;
  • 如果節(jié)點(diǎn)是標(biāo)簽或者元素,就做 Element Diff;

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

component diff(組件比較)

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

對(duì)于同一類(lèi)型的組件,有可能其 Virtual DOM 沒(méi)有任何變化,如果能夠確切的知道這點(diǎn)那可以節(jié)省大量的 diff 運(yùn)算時(shí)間。因此 React 允許用戶通過(guò) shouldComponentUpdate() 來(lái)判斷該組件是否需要進(jìn)行 diff。

對(duì)于兩個(gè)不同類(lèi)型但結(jié)構(gòu)相似的組件,不會(huì)比較二者的結(jié)構(gòu),而且替換整個(gè)組件的所有內(nèi)容。不同類(lèi)型的 component 是很少存在相似 DOM tree 的機(jī)會(huì),因此這種極端因素很難在實(shí)現(xiàn)開(kāi)發(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 類(lèi)型不在老集合里, 即是全新的節(jié)點(diǎn),需要對(duì)新節(jié)點(diǎn)執(zhí)行插入操作。
  • MOVE_EXISTING,在老集合有新 component 類(lèi)型,且 element 是可更新的類(lèi)型,這種情況下需要做移動(dòng)操作,可以復(fù)用以前的 DOM 節(jié)點(diǎn)。
  • REMOVE_NODE,老 component 類(lèi)型,在新集合里也有,但對(duì)應(yīng)的 element 不同則不能直接復(fù)用和更新,需要執(zhí)行刪除操作,或者老 component 不在新集合里的,也需要執(zhí)行刪除操作。
  1. <ul> 
  2.   <li>Duke</li> 
  3.   <li>Villanova</li> 
  4. </ul> 
  5.  
  6. <ul> 
  7.   <li>Connecticut</li> 
  8.   <li>Duke</li> 
  9.   <li>Villanova</li> 
  10. </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)匹配原有樹(shù)上的子元素以及最新樹(shù)上的子元素。如果有相同的節(jié)點(diǎn),無(wú)需進(jìn)行節(jié)點(diǎn)刪除和創(chuàng)建,只需要將老集合中節(jié)點(diǎn)的位置進(jìn)行移動(dòng),更新為新集合中節(jié)點(diǎn)的位置。

  • 在開(kāi)發(fā)過(guò)程中,盡量減少類(lèi)似將最后一個(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)聽(tīng)數(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ù)流,稱(chēng)之為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)模板中的常見(jià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è)組件樹(shù)。

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)開(kāi)啟這一功能。

4. 圖片優(yōu)化

  1. 圖片懶加載
  2. 響應(yīng)式圖片:瀏覽器根據(jù)屏幕大小自動(dòng)加載合適的圖片。
  3. 降低圖片質(zhì)量:方法有兩種,一是通過(guò) webpack 插件 image-webpack-loader,二是通過(guò)在線網(wǎng)站進(jìn)行壓縮。

5. 減少重繪重排

  • 降低 CSS 選擇器的復(fù)雜性
  • 使用 transform 和 opacity 屬性更改來(lái)實(shí)現(xiàn)動(dòng)畫(huà)
  • 用 JavaScript 修改樣式時(shí),最好不要直接寫(xiě)樣式,而是替換 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)畫(huà),并且要求瀏覽器在下次重繪之前調(diào)用指定的回調(diào)函數(shù)更新動(dòng)畫(huà)。該方法需要傳入一個(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ā)生了什么

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

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ì)分開(kāi)發(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樹(shù),解析CSS,生成CSSOM樹(shù),然后通過(guò)DOM樹(shù)和CSSPOM樹(shù)生成渲染樹(shù)。渲染樹(shù)與DOM樹(shù)不同,渲染樹(shù)中并沒(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ù)渲染樹(shù)布局,計(jì)算CSS樣式,即每個(gè)節(jié)點(diǎn)在頁(yè)面中的大小和位置等幾何信息。HTML默認(rèn)是流式布局的,CSS和js會(huì)打破這種布局,改變DOM的外觀樣式以及大小和位置。最后瀏覽器繪制各個(gè)節(jié)點(diǎn),將頁(yè)面展示給用戶。

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

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

參考:

細(xì)說(shuō)瀏覽器輸入U(xiǎn)RL后發(fā)生了什么

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

前端路由

什么是路由

路由是用來(lái)跟后端服務(wù)器進(jìn)行交互的一種方式,通過(guò)不同的路徑請(qǐng)求不同的資源。

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

前端路由

Ajax,全稱(chēng) 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)聽(tīng)hash值的改變。

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

2.History 模式

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

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

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

  1. history.pushState();   // 向當(dāng)前瀏覽器會(huì)話的歷史堆棧中添加一個(gè)狀態(tài) 
  2. history.replaceState();// 修改了當(dāng)前的歷史記錄項(xiàng)(不是新建一個(gè)) 
  3. 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)聽(tīng)歷史棧的改變,一旦歷史棧信息發(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事件。
  1. // 歷史棧改變 
  2. 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,開(kāi)發(fā)者可以提前用上新的JS特性。

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

Plugin

實(shí)現(xiàn)Babel代碼轉(zhuǎn)換功能的核心,就是Babel插件(plugin)。Babel插件一般盡可能拆成小的力度,開(kāi)發(fā)者可以按需引進(jìn), 既提高了性能,也提高了擴(kuò)展性。比如對(duì)ES6轉(zhuǎn)ES5的功能,Babel官方拆成了20+個(gè)插件。開(kāi)發(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í)行順序非常重要。

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

比如.babelrc配置如下,那么執(zhí)行的順序?yàn)椋?/p>

  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.   ] 

怎樣開(kāi)發(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)資源?

  1. 先部署頁(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ò)誤。
  2. 先部署資源,再部署頁(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è)東西:

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

大數(shù)相加

  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

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

  1. function fib(n) { 
  2.     if (n <= 0) { 
  3.         return 0; 
  4.     } 
  5.     let n1 = 1; 
  6.     let n2 = 1; 
  7.     let sum = 1; 
  8.     for(let i = 3; i <= n; i++) { 
  9.         [n1, n2] = [n2, sum]; 
  10.         sum = n1 + n2; 
  11.     } 
  12.     return sum
  13. }; 

 javascript前端react.js

本文完〜

 

責(zé)任編輯:姜華 來(lái)源: 大遷世界
相關(guān)推薦

2021-03-02 07:51:17

前端開(kāi)發(fā)技術(shù)熱點(diǎn)

2021-03-01 08:03:16

前端開(kāi)發(fā)技術(shù)熱點(diǎn)

2024-05-22 08:03:39

2010-05-24 16:58:00

MySQL數(shù)據(jù)庫(kù)備份

2018-02-01 13:59:44

Linux命令磁盤(pán)管理

2021-04-01 13:44:50

開(kāi)發(fā)前端Web

2009-09-23 11:07:11

Hibernate基礎(chǔ)

2010-07-16 10:53:30

Perl基礎(chǔ)

2009-04-17 14:22:40

XPathXML基礎(chǔ)

2015-06-01 13:35:43

數(shù)據(jù)中心DCIM

2018-08-30 11:11:32

前端程序員基礎(chǔ)知識(shí)

2011-09-16 10:13:02

Emacs

2011-03-29 14:11:20

Cacti基礎(chǔ)知識(shí)

2009-04-10 09:35:00

WCDMA基礎(chǔ)無(wú)線網(wǎng)絡(luò)

2023-07-04 07:31:06

MapReduce數(shù)據(jù)處理編程模型

2010-07-16 11:22:31

Perl

2014-08-20 10:15:45

2021-11-05 15:31:01

UbuntuLinux

2022-09-09 16:04:58

Linux設(shè)備樹(shù)

2015-09-17 10:48:17

Web前端
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)