(web前端) 框架高頻面試題

***************** Vue 面試題 *****************

1.聊聊對(duì)vue的理解

vue是一個(gè)漸進(jìn)式的JS框架。他易用,靈活,高效; 可以把一個(gè)頁面分隔成多個(gè)組件;當(dāng)其他頁面有類似功能時(shí),直接讓封裝的組件進(jìn)行復(fù)用; 他是構(gòu)建用戶界面的聲明式框架,只關(guān)心圖層;不關(guān)心具體是如何實(shí)現(xiàn)的

2.V-model的原理是什么?

Vue的雙向數(shù)據(jù)綁定是由數(shù)據(jù)劫持結(jié)合發(fā)布者訂閱者實(shí)現(xiàn)的。 數(shù)據(jù)劫持是通過Object.defineProperty()來劫持對(duì)象數(shù)據(jù)的setter和getter操作。 在數(shù)據(jù)變動(dòng)時(shí)作你想做的事

  • 原理 通過Observer來監(jiān)聽自己的model數(shù)據(jù)變化,通過Compile來解析編譯模板指令,最終利用Watcher搭起Observer和Compile之間的通信橋梁,達(dá)到數(shù)據(jù)變化->視圖更新 在初始化vue實(shí)例時(shí),遍歷data這個(gè)對(duì)象,給每一個(gè)鍵值對(duì)利用Object.definedProperty對(duì)data的鍵值對(duì)新增get和set方法,利用了事件監(jiān)聽DOM的機(jī)制,讓視圖去改變數(shù)據(jù)

3.VUE和REACT有什么區(qū)別?

react整體是函數(shù)式的思想,把組件設(shè)計(jì)成純組件,狀態(tài)和邏輯通過參數(shù)傳入,所以在react中,是單向數(shù)據(jù)流;

vue的思想是響應(yīng)式的,也就是基于是數(shù)據(jù)可變的,通過對(duì)每一個(gè)屬性建立Watcher來監(jiān)聽,當(dāng)屬性變化的時(shí)候,響應(yīng)式的更新對(duì)應(yīng)的虛擬dom。

4.vue路由的兩種模式

  • hash ——即地址欄URL中的#符號(hào)(此hsah 不是密碼學(xué)里的散列運(yùn)算) hash 雖然出現(xiàn)URL中,但不會(huì)被包含在HTTP請(qǐng)求中,對(duì)后端完全沒有影響,因此改變hash不會(huì)重新加載頁面。
  • history ——利用了HTML5 History Interface 中新增的pushState() 和replaceState() 方法

這兩個(gè)方法應(yīng)用于瀏覽器的歷史記錄站,在當(dāng)前已有的back、forward、go 的基礎(chǔ)之上,它們提供了對(duì)歷史記錄進(jìn)行修改的功能。只是當(dāng)它們執(zhí)行修改是,雖然改變了當(dāng)前的URL,但你瀏覽器不會(huì)立即向后端發(fā)送請(qǐng)求。

5.vue中 key 值的作用

當(dāng) Vue.js 用v-for正在更新已渲染過的元素列表時(shí),它默認(rèn)用“就地復(fù)用”策略。 如果數(shù)據(jù)項(xiàng)的順序被改變,Vue 將不會(huì)移動(dòng) DOM 元素來匹配數(shù)據(jù)項(xiàng)的順序,而是簡單復(fù)用此處每個(gè)元素,并且確保它在特定索引下顯示已被渲染過的每個(gè)元素。

key的作用主要是為了高效的更新虛擬DOM。

6.$route$router的區(qū)別

  • $route是“路由信息對(duì)象”,包括path,params,hash,query,fullPath,matched,name等路由信息參數(shù)。
  • $router是“路由實(shí)例”對(duì)象包括了路由的跳轉(zhuǎn)方法,鉤子函數(shù)等。

7.vue修飾符

  • stop:阻止事件的冒泡
  • prevent:阻止事件的默認(rèn)行為
  • once:只觸發(fā)一次
  • self:只觸發(fā)自己的事件行為時(shí),才會(huì)執(zhí)行

8.vue.extend和vue.component

extend 是構(gòu)造一個(gè)組件的語法器。 然后這個(gè)組件你可以作用到Vue.component這個(gè)全局注冊(cè)方法里 還可以在任意vue模板里使用組件。 也可以作用到vue實(shí)例或者某個(gè)組件中的components屬性中并在內(nèi)部使用apple組件。

Vue.component 你可以創(chuàng)建 ,也可以取組件。

9.Vue的SPA 如何優(yōu)化加載速度

1.減少入口文件體積
2.靜態(tài)資源本地緩存
3.開啟Gzip壓縮
4.使用SSR,nuxt.js

10.Proxy比defineproperty優(yōu)劣對(duì)比?

雙向綁定其實(shí)已經(jīng)是一個(gè)老掉牙的問題了,只要涉及到MVVM框架就不得不談的知識(shí)點(diǎn),但它畢竟是Vue的三要素之一.

Vue三要素:

  • 響應(yīng)式: 例如如何監(jiān)聽數(shù)據(jù)變化,其中的實(shí)現(xiàn)方法就是我們提到的雙向綁定
  • 模板引擎: 如何解析模板
  • 渲染: Vue如何將監(jiān)聽到的數(shù)據(jù)變化和解析后的HTML進(jìn)行渲染

可以實(shí)現(xiàn)雙向綁定的方法有很多,KnockoutJS基于觀察者模式的雙向綁定,Ember基于數(shù)據(jù)模型的雙向綁定,Angular基于臟檢查的雙向綁定,本篇文章我們重點(diǎn)講面試中常見的基于數(shù)據(jù)劫持的雙向綁定。

常見的基于數(shù)據(jù)劫持的雙向綁定有兩種實(shí)現(xiàn),一個(gè)是目前Vue在用的Object.defineProperty,另一個(gè)是ES2015中新增的Proxy,而Vue的作者宣稱將在Vue3.0版本后加入Proxy從而代替Object.defineProperty

嚴(yán)格來講Proxy應(yīng)該被稱為『代理』而非『劫持』,不過由于作用有很多相似之處,我們?cè)谙挛闹芯筒辉僮鰠^(qū)分,統(tǒng)一叫『劫持』。

Object.defineProperty的缺陷:

  • Object.defineProperty的第一個(gè)缺陷,無法監(jiān)聽數(shù)組變化。 然而Vue的文檔提到了Vue是可以檢測(cè)到數(shù)組變化的,但是只有以下八種方法,vm.items[indexOfItem] = newValue這種是無法檢測(cè)的

  • Object.defineProperty的第二個(gè)缺陷,只能劫持對(duì)象的屬性,因此我們需要對(duì)每個(gè)對(duì)象的每個(gè)屬性進(jìn)行遍歷,如果屬性值也是對(duì)象那么需要深度遍歷,顯然能劫持一個(gè)完整的對(duì)象是更好的選擇。

Proxy可以直接監(jiān)聽對(duì)象而非屬性
Proxy可以直接監(jiān)聽數(shù)組的變化

Proxy有多達(dá)13種攔截方法,不限于apply、ownKeys、deleteProperty、has等等是Object.defineProperty不具備的。

Proxy返回的是一個(gè)新對(duì)象,我們可以只操作新的對(duì)象達(dá)到目的,而Object.defineProperty只能遍歷對(duì)象屬性直接修改。

Proxy作為新標(biāo)準(zhǔn)將受到瀏覽器廠商重點(diǎn)持續(xù)的性能優(yōu)化,也就是傳說中的新標(biāo)準(zhǔn)的性能紅利。

11.既然Vue通過數(shù)據(jù)劫持可以精準(zhǔn)探測(cè)數(shù)據(jù)變化,為什么還需要虛擬DOM進(jìn)行diff檢測(cè)差異?

考點(diǎn): Vue的變化偵測(cè)原理

前置知識(shí): 依賴收集、虛擬DOM、響應(yīng)式系統(tǒng)

現(xiàn)代前端框架有兩種方式偵測(cè)變化,一種是pull一種是push

pull: 其代表為React,我們可以回憶一下React是如何偵測(cè)到變化的,我們通常會(huì)用setStateAPI顯式更新,然后React會(huì)進(jìn)行一層層的Virtual Dom Diff操作找出差異,然后Patch到DOM上,React從一開始就不知道到底是哪發(fā)生了變化,只是知道「有變化了」,然后再進(jìn)行比較暴力的Diff操作查找「哪發(fā)生變化了」,另外一個(gè)代表就是Angular的臟檢查操作。

push: Vue的響應(yīng)式系統(tǒng)則是push的代表,當(dāng)Vue程序初始化的時(shí)候就會(huì)對(duì)數(shù)據(jù)data進(jìn)行依賴的收集,一但數(shù)據(jù)發(fā)生變化,響應(yīng)式系統(tǒng)就會(huì)立刻得知,因此Vue是一開始就知道是「在哪發(fā)生變化了」,但是這又會(huì)產(chǎn)生一個(gè)問題,如果你熟悉Vue的響應(yīng)式系統(tǒng)就知道,通常一個(gè)綁定一個(gè)數(shù)據(jù)就需要一個(gè)Watcher,一但我們的綁定細(xì)粒度過高就會(huì)產(chǎn)生大量的Watcher,這會(huì)帶來內(nèi)存以及依賴追蹤的開銷,而細(xì)粒度過低會(huì)無法精準(zhǔn)偵測(cè)變化,因此Vue的設(shè)計(jì)是選擇中等細(xì)粒度的方案,在組件級(jí)別進(jìn)行push偵測(cè)的方式,也就是那套響應(yīng)式系統(tǒng),通常我們會(huì)第一時(shí)間偵測(cè)到發(fā)生變化的組件,然后在組件內(nèi)部進(jìn)行Virtual Dom Diff獲取更加具體的差異,而Virtual Dom Diff則是pull操作,Vue是push+pull結(jié)合的方式進(jìn)行變化偵測(cè)的.

12.Vue為什么沒有類似于React中shouldComponentUpdate的生命周期?

React是pull的方式偵測(cè)變化,當(dāng)React知道發(fā)生變化后,會(huì)使用Virtual Dom Diff進(jìn)行差異檢測(cè),但是很多組件實(shí)際上是肯定不會(huì)發(fā)生變化的,這個(gè)時(shí)候需要用shouldComponentUpdate進(jìn)行手動(dòng)操作來減少diff,從而提高程序整體的性能.

Vue是pull+push的方式偵測(cè)變化的,在一開始就知道那個(gè)組件發(fā)生了變化,因此在push的階段并不需要手動(dòng)控制diff,而組件內(nèi)部采用的diff方式實(shí)際上是可以引入類似于shouldComponentUpdate相關(guān)生命周期的,但是通常合理大小的組件不會(huì)有過量的diff,手動(dòng)優(yōu)化的價(jià)值有限,因此目前Vue并沒有考慮引入shouldComponentUpdate這種手動(dòng)優(yōu)化的生命周期.

***************** React 面試題 *****************

1.redux中的reducer(純函數(shù))

Redux數(shù)據(jù)流里,reduces其實(shí)是根據(jù)之前的狀態(tài)(previous state)和現(xiàn)有的action(current action) 更新state(這個(gè)state可以理解為上下累加器的結(jié)果) 每次redux reducer被執(zhí)行時(shí),state和action被傳入,這個(gè)state根據(jù)action進(jìn)行累加或者是'自身消減'(reduce), 進(jìn)而返回最新的state,這也就是典型reduce函數(shù)的用法:state -> action -> state

2.react的refs

refs就想一個(gè)逃生窗,允許我們之間訪問dom元素或者組件實(shí)例,可以向組件添加一個(gè)ref屬性的值是一個(gè)回調(diào)函數(shù),
它將接受地城dom元素或組件的已掛在實(shí)例,作為第一個(gè)參數(shù)

3.react中的keys

幫組我們跟蹤哪些項(xiàng)目已更改、添加、從列表中刪除,key是獨(dú)一無二的,可以讓我們高效的去定位元素,并且操作它

4.diff算法

  • 1.把樹形結(jié)構(gòu)按照層級(jí)分解,只比較同級(jí)元素

  • 2.給列表結(jié)構(gòu)的每個(gè)單元添加key屬性,方便比較。在實(shí)際代碼中,會(huì)對(duì)新舊兩棵樹進(jìn)行一個(gè)深度優(yōu)先的遍歷,這樣每個(gè)節(jié)點(diǎn)都會(huì)有一個(gè)標(biāo)記

  • 3.在深度優(yōu)先遍歷的時(shí)候,每遍歷到一個(gè)節(jié)點(diǎn)就把該節(jié)點(diǎn)和新的樹進(jìn)行對(duì)比。如果有差異的話就記錄到一個(gè)對(duì)象里面 Vritual DOM 算法主要實(shí)現(xiàn)上面步驟的三個(gè)函數(shù):element, diff, patch。然后就可以實(shí)際的進(jìn)行使用 react只會(huì)匹配相同的class的component(這里的class指的是組件的名字) 合并操作,條用component的setState方法的時(shí)候,React將其標(biāo)記為dirty.到每一個(gè)時(shí)間循環(huán)借宿,React檢查所有標(biāo)記dirty的component重新繪制

  • 4.選擇性子樹渲染??梢灾貙憇houldComponentUpdate提高diff的性能

5.簡述下flux的思想

flux的最大特點(diǎn),就是數(shù)據(jù)的‘單向流動(dòng)’
1.用戶訪問View
2.View發(fā)出用戶的Action
3.Dispatcher收到Action,要求state進(jìn)行相應(yīng)的更新
4.store更新后,發(fā)出一個(gè)‘change’事件后,更新頁面

6.reac性能優(yōu)化是哪個(gè)周期函數(shù)

shouldComponentUpdate 這個(gè)方法用來判斷是否需要調(diào)用render方法重新描繪dom.因?yàn)閐om的描繪非常消耗性能,
如果我們?cè)趕houldComponentUpdate方法中能夠?qū)懗龈鼉?yōu)化的dom diff算法,可以極大的提高性能

7.react怎么劃分業(yè)務(wù)組件和技術(shù)組件

根據(jù)組件的職責(zé)通常把組件分為UI組件和容器組件
UI組件負(fù)責(zé)UI的呈現(xiàn),容器組件負(fù)責(zé)管理數(shù)據(jù)和邏輯
兩者通過React-redux提供connect方法聯(lián)系起來

8.說下setState這個(gè)方法

setState通過一個(gè)隊(duì)列機(jī)制實(shí)現(xiàn)state更新,當(dāng)執(zhí)行setState時(shí),會(huì)將需要更新的state很后放入狀態(tài)隊(duì)列
而不會(huì)立即更新this.state,隊(duì)列機(jī)制可以高效地批量更新state。如果不通過setState而直接修改this.state的值 
那么該state將不會(huì)被放入狀態(tài)隊(duì)列中。當(dāng)下次調(diào)用setState并對(duì)狀態(tài)隊(duì)列進(jìn)行合并時(shí),就會(huì)忽略之前修改的state,造成不可預(yù)知的錯(cuò)誤

同時(shí),也利用了隊(duì)列機(jī)制實(shí)現(xiàn)了setState的異步更新,避免了頻繁的重復(fù)更新state

同步更新state:
    setState 函數(shù)并不會(huì)阻塞等待狀態(tài)更新完畢,因此 setNetworkActivityIndicatorVisible 有可能先于數(shù)據(jù)渲染完畢就執(zhí)行。
    第二個(gè)參數(shù)是一個(gè)回調(diào)函數(shù),在setState的異步操作結(jié)束并且組件已經(jīng)重新渲染的時(shí)候執(zhí)行
    也就是說,我們可以通過這個(gè)回調(diào)來拿到更新的state的值,實(shí)現(xiàn)代碼的同步

例子:componentDidMount() {

    fetch('https://test.com')
    
    .then((res) => res.json())
    
    .then(
    (data) => {
this.setState({ data:data });
            StatusBar.setNetworkActivityIndicatorVisible(false);
        }

9.React最新的生命周期是怎樣的?

React 16之后有三個(gè)生命周期被廢棄(但并未刪除)

  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate

官方計(jì)劃在17版本完全刪除這三個(gè)函數(shù),只保留UNSAVE_前綴的三個(gè)函數(shù),目的是為了向下兼容,但是對(duì)于開發(fā)者而言應(yīng)該盡量避免使用他們,而是使用新增的生命周期函數(shù)替代它們

目前React 16.8 +的生命周期分為三個(gè)階段,分別是掛載階段、更新階段、卸載階段

掛載階段:

  • constructor: 構(gòu)造函數(shù),最先被執(zhí)行,我們通常在構(gòu)造函數(shù)里初始化state對(duì)象或者給自定義方法綁定this
  • getDerivedStateFromProps: static getDerivedStateFromProps(nextProps, prevState),這是個(gè)靜態(tài)方法,當(dāng)我們接收到新的屬性想去修改我們state,可以使用getDerivedStateFromProps
  • render: render函數(shù)是純函數(shù),只返回需要渲染的東西,不應(yīng)該包含其它的業(yè)務(wù)邏輯,可以返回原生的DOM、React組件、Fragment、Portals、字符串和數(shù)字、Boolean和null等內(nèi)容
  • componentDidMount: 組件裝載之后調(diào)用,此時(shí)我們可以獲取到DOM節(jié)點(diǎn)并操作,比如對(duì)canvas,svg的操作,服務(wù)器請(qǐng)求,訂閱都可以寫在這個(gè)里面,但是記得在componentWillUnmount中取消訂閱

更新階段:

  • getDerivedStateFromProps: 此方法在更新個(gè)掛載階段都可能會(huì)調(diào)用
  • shouldComponentUpdate: shouldComponentUpdate(nextProps, nextState),有兩個(gè)參數(shù)nextProps和nextState,表示新的屬性和變化之后的state,返回一個(gè)布爾值,true表示會(huì)觸發(fā)重新渲染,false表示不會(huì)觸發(fā)重新渲染,默認(rèn)返回true,我們通常利用此生命周期來優(yōu)化React程序性能
  • render: 更新階段也會(huì)觸發(fā)此生命周期
  • getSnapshotBeforeUpdate: getSnapshotBeforeUpdate(prevProps, prevState),這個(gè)方法在render之后,componentDidUpdate之前調(diào)用,有兩個(gè)參數(shù)prevProps和prevState,表示之前的屬性和之前的state,這個(gè)函數(shù)有一個(gè)返回值,會(huì)作為第三個(gè)參數(shù)傳給componentDidUpdate,如果你不想要返回值,可以返回null,此生命周期必須與componentDidUpdate搭配使用
  • componentDidUpdate: componentDidUpdate(prevProps, prevState, snapshot),該方法在getSnapshotBeforeUpdate方法之后被調(diào)用,有三個(gè)參數(shù)prevProps,prevState,snapshot,表示之前的props,之前的state,和snapshot。第三個(gè)參數(shù)是getSnapshotBeforeUpdate返回的,如果觸發(fā)某些回調(diào)函數(shù)時(shí)需要用到 DOM 元素的狀態(tài),則將對(duì)比或計(jì)算的過程遷移至 getSnapshotBeforeUpdate,然后在 componentDidUpdate 中統(tǒng)一觸發(fā)回調(diào)或更新狀態(tài)。

卸載階段:

  • componentWillUnmount: 當(dāng)我們的組件被卸載或者銷毀了就會(huì)調(diào)用,我們可以在這個(gè)函數(shù)里去清除一些定時(shí)器,取消網(wǎng)絡(luò)請(qǐng)求,清理無效的DOM元素等垃圾清理工作

10.React的請(qǐng)求應(yīng)該放在哪個(gè)生命周期中?

React的異步請(qǐng)求到底應(yīng)該放在哪個(gè)生命周期里,有人認(rèn)為在componentWillMount中可以提前進(jìn)行異步請(qǐng)求,避免白屏,其實(shí)這個(gè)觀點(diǎn)是有問題的.

由于JavaScript中異步事件的性質(zhì),當(dāng)您啟動(dòng)API調(diào)用時(shí),瀏覽器會(huì)在此期間返回執(zhí)行其他工作。當(dāng)React渲染一個(gè)組件時(shí),它不會(huì)等待componentWillMount它完成任何事情 - React繼續(xù)前進(jìn)并繼續(xù)render,沒有辦法“暫停”渲染以等待數(shù)據(jù)到達(dá)。

而且在componentWillMount請(qǐng)求會(huì)有一系列潛在的問題,首先,在服務(wù)器渲染時(shí),如果在 componentWillMount 里獲取數(shù)據(jù),fetch data會(huì)執(zhí)行兩次,一次在服務(wù)端一次在客戶端,這造成了多余的請(qǐng)求,其次,在React 16進(jìn)行React Fiber重寫后,componentWillMount可能在一次渲染中多次調(diào)用.

目前官方推薦的異步請(qǐng)求是在componentDidmount中進(jìn)行.

如果有特殊需求需要提前請(qǐng)求,也可以在特殊情況下在constructor中請(qǐng)求:

react 17之后componentWillMount會(huì)被廢棄,僅僅保留UNSAFE_componentWillMount

11.setState到底是異步還是同步?

先給出答案: 有時(shí)表現(xiàn)出異步,有時(shí)表現(xiàn)出同步

  1. setState 只在合成事件和鉤子函數(shù)中是“異步”的,在原生事件和setTimeout 中都是同步的。
  2. setState 的“異步”并不是說內(nèi)部由異步代碼實(shí)現(xiàn),其實(shí)本身執(zhí)行的過程和代碼都是同步的,只是合成事件和鉤子函數(shù)的調(diào)用順序在更新之前,導(dǎo)致在合成事件和鉤子函數(shù)中沒法立馬拿到更新后的值,形成了所謂的“異步”,當(dāng)然可以通過第二個(gè)參數(shù) setState(partialState, callback) 中的callback拿到更新后的結(jié)果。
  3. setState 的批量更新優(yōu)化也是建立在“異步”(合成事件、鉤子函數(shù))之上的,在原生事件和setTimeout 中不會(huì)批量更新,在“異步”中如果對(duì)同一個(gè)值進(jìn)行多次setState,setState的批量更新策略會(huì)對(duì)其進(jìn)行覆蓋,取最后一次的執(zhí)行,如果是同時(shí)setState多個(gè)不同的值,在更新時(shí)會(huì)對(duì)其進(jìn)行合并批量更新。

12.React組件通信如何實(shí)現(xiàn)?

React組件間通信方式:

  • 父組件向子組件通訊: 父組件可以向子組件通過傳 props 的方式,向子組件進(jìn)行通訊
  • 子組件向父組件通訊: props+回調(diào)的方式,父組件向子組件傳遞props進(jìn)行通訊,此props為作用域?yàn)楦附M件自身的函數(shù),子組件調(diào)用該函數(shù),將子組件想要傳遞的信息,作為參數(shù),傳遞到父組件的作用域中
  • 兄弟組件通信: 找到這兩個(gè)兄弟節(jié)點(diǎn)共同的父節(jié)點(diǎn),結(jié)合上面兩種方式由父節(jié)點(diǎn)轉(zhuǎn)發(fā)信息進(jìn)行通信
  • 跨層級(jí)通信: Context設(shè)計(jì)目的是為了共享那些對(duì)于一個(gè)組件樹而言是“全局”的數(shù)據(jù),例如當(dāng)前認(rèn)證的用戶、主題或首選語言,對(duì)于跨越多層的全局?jǐn)?shù)據(jù)通過Context通信再適合不過
  • 發(fā)布訂閱模式: 發(fā)布者發(fā)布事件,訂閱者監(jiān)聽事件并做出反應(yīng),我們可以通過引入event模塊進(jìn)行通信
  • 全局狀態(tài)管理工具: 借助Redux或者M(jìn)obx等全局狀態(tài)管理工具進(jìn)行通信,這種工具會(huì)維護(hù)一個(gè)全局狀態(tài)中心Store,并根據(jù)不同的事件產(chǎn)生新的狀態(tài)

13.React如何進(jìn)行組件/邏輯復(fù)用?

拋開已經(jīng)被官方棄用的Mixin,組件抽象的技術(shù)目前有三種比較主流:

  • 高階組件:
    • 屬性代理
    • 反向繼承
  • 渲染屬性
  • react-hooks

14.mixin、hoc、render props、react-hooks的優(yōu)劣如何?

Mixin的缺陷:

  • 組件與 Mixin 之間存在隱式依賴(Mixin 經(jīng)常依賴組件的特定方法,但在定義組件時(shí)并不知道這種依賴關(guān)系)
  • 多個(gè) Mixin 之間可能產(chǎn)生沖突(比如定義了相同的state字段)
  • Mixin 傾向于增加更多狀態(tài),這降低了應(yīng)用的可預(yù)測(cè)性(The more state in your application, the harder it is to reason about it.),導(dǎo)致復(fù)雜度劇增
  • 隱式依賴導(dǎo)致依賴關(guān)系不透明,維護(hù)成本和理解成本迅速攀升:
    • 難以快速理解組件行為,需要全盤了解所有依賴 Mixin 的擴(kuò)展行為,及其之間的相互影響
    • 組價(jià)自身的方法和state字段不敢輕易刪改,因?yàn)殡y以確定有沒有 Mixin 依賴它
    • Mixin 也難以維護(hù),因?yàn)?Mixin 邏輯最后會(huì)被打平合并到一起,很難搞清楚一個(gè) Mixin 的輸入輸出

HOC相比Mixin的優(yōu)勢(shì):

  • HOC通過外層組件通過 Props 影響內(nèi)層組件的狀態(tài),而不是直接改變其 State不存在沖突和互相干擾,這就降低了耦合度
  • 不同于 Mixin 的打平+合并,HOC 具有天然的層級(jí)結(jié)構(gòu)(組件樹結(jié)構(gòu)),這又降低了復(fù)雜度

HOC的缺陷:

  • 擴(kuò)展性限制: HOC 無法從外部訪問子組件的 State因此無法通過shouldComponentUpdate濾掉不必要的更新,React 在支持 ES6 Class 之后提供了React.PureComponent來解決這個(gè)問題
  • Ref 傳遞問題: Ref 被隔斷,后來的React.forwardRef 來解決這個(gè)問題
  • Wrapper Hell: HOC可能出現(xiàn)多層包裹組件的情況,多層抽象同樣增加了復(fù)雜度和理解成本
  • 命名沖突: 如果高階組件多次嵌套,沒有使用命名空間的話會(huì)產(chǎn)生沖突,然后覆蓋老屬性
  • 不可見性: HOC相當(dāng)于在原有組件外層再包裝一個(gè)組件,你壓根不知道外層的包裝是啥,對(duì)于你是黑盒

Render Props優(yōu)點(diǎn):

  • 上述HOC的缺點(diǎn)Render Props都可以解決

Render Props缺陷:

  • 使用繁瑣: HOC使用只需要借助裝飾器語法通常一行代碼就可以進(jìn)行復(fù)用,Render Props無法做到如此簡單
  • 嵌套過深: Render Props雖然擺脫了組件多層嵌套的問題,但是轉(zhuǎn)化為了函數(shù)回調(diào)的嵌套

React Hooks優(yōu)點(diǎn):

  • 簡潔: React Hooks解決了HOC和Render Props的嵌套問題,更加簡潔
  • 解耦: React Hooks可以更方便地把 UI 和狀態(tài)分離,做到更徹底的解耦
  • 組合: Hooks 中可以引用另外的 Hooks形成新的Hooks,組合變化萬千
  • 函數(shù)友好: React Hooks為函數(shù)組件而生,從而解決了類組件的幾大問題:
    • this 指向容易錯(cuò)誤
    • 分割在不同聲明周期中的邏輯使得代碼難以理解和維護(hù)
    • 代碼復(fù)用成本高(高階組件容易使代碼量劇增)

React Hooks缺陷:

  • 額外的學(xué)習(xí)成本(Functional Component 與 Class Component 之間的困惑)
  • 寫法上有限制(不能出現(xiàn)在條件、循環(huán)中),并且寫法限制增加了重構(gòu)成本
  • 破壞了PureComponent、React.memo淺比較的性能優(yōu)化效果(為了取最新的props和state,每次render()都要重新創(chuàng)建事件處函數(shù))
  • 在閉包場(chǎng)景可能會(huì)引用到舊的state、props值
  • 內(nèi)部實(shí)現(xiàn)上不直觀(依賴一份可變的全局狀態(tài),不再那么“純”)
  • React.memo并不能完全替代shouldComponentUpdate(因?yàn)槟貌坏?state change,只針對(duì) props change)

15.你是如何理解fiber的?

React Fiber 是一種基于瀏覽器的單線程調(diào)度算法.

React 16之前 ,reconcilation 算法實(shí)際上是遞歸,想要中斷遞歸是很困難的,React 16 開始使用了循環(huán)來代替之前的遞歸.

Fiber一種將 recocilation (遞歸 diff),拆分成無數(shù)個(gè)小任務(wù)的算法;它隨時(shí)能夠停止,恢復(fù)。停止恢復(fù)的時(shí)機(jī)取決于當(dāng)前的一幀(16ms)內(nèi),還有沒有足夠的時(shí)間允許計(jì)算。

16。你對(duì) Time Slice的理解?

時(shí)間分片

  • React 在渲染(render)的時(shí)候,不會(huì)阻塞現(xiàn)在的線程
  • 如果你的設(shè)備足夠快,你會(huì)感覺渲染是同步的
  • 如果你設(shè)備非常慢,你會(huì)感覺還算是靈敏的
  • 雖然是異步渲染,但是你將會(huì)看到完整的渲染,而不是一個(gè)組件一行行的渲染出來
  • 同樣書寫組件的方式

也就是說,這是React背后在做的事情,對(duì)于我們開發(fā)者來說,是透明的。時(shí)間分片正是基于可隨時(shí)打斷、重啟的Fiber架構(gòu),可打斷當(dāng)前任務(wù),優(yōu)先處理緊急且重要的任務(wù),保證頁面的流暢運(yùn)行.

17.redux的工作流程?

首先,我們看下幾個(gè)核心概念:

  • Store:保存數(shù)據(jù)的地方,你可以把它看成一個(gè)容器,整個(gè)應(yīng)用只能有一個(gè)Store。
  • State:Store對(duì)象包含所有數(shù)據(jù),如果想得到某個(gè)時(shí)點(diǎn)的數(shù)據(jù),就要對(duì)Store生成快照,這種時(shí)點(diǎn)的數(shù)據(jù)集合,就叫做State。
  • Action:State的變化,會(huì)導(dǎo)致View的變化。但是,用戶接觸不到State,只能接觸到View。所以,State的變化必須是View導(dǎo)致的。Action就是View發(fā)出的通知,表示State應(yīng)該要發(fā)生變化了。
  • Action Creator:View要發(fā)送多少種消息,就會(huì)有多少種Action。如果都手寫,會(huì)很麻煩,所以我們定義一個(gè)函數(shù)來生成Action,這個(gè)函數(shù)就叫Action Creator。
  • Reducer:Store收到Action以后,必須給出一個(gè)新的State,這樣View才會(huì)發(fā)生變化。這種State的計(jì)算過程就叫做Reducer。Reducer是一個(gè)函數(shù),它接受Action和當(dāng)前State作為參數(shù),返回一個(gè)新的State。
  • dispatch:是View發(fā)出Action的唯一方法。

然后我們過下整個(gè)工作流程:

  1. 首先,用戶(通過View)發(fā)出Action,發(fā)出方式就用到了dispatch方法。
  2. 然后,Store自動(dòng)調(diào)用Reducer,并且傳入兩個(gè)參數(shù):當(dāng)前State和收到的Action,Reducer會(huì)返回新的State
  3. State一旦有變化,Store就會(huì)調(diào)用監(jiān)聽函數(shù),來更新View。

到這兒為止,一次用戶交互流程結(jié)束??梢钥吹剑谡麄€(gè)流程中數(shù)據(jù)都是單向流動(dòng)的,這種方式保證了流程的清晰。

18.redux與mobx的區(qū)別?

兩者對(duì)比:

  • redux將數(shù)據(jù)保存在單一的store中,mobx將數(shù)據(jù)保存在分散的多個(gè)store中
  • redux使用plain object保存數(shù)據(jù),需要手動(dòng)處理變化后的操作;mobx適用observable保存數(shù)據(jù),數(shù)據(jù)變化后自動(dòng)處理響應(yīng)的操作
  • redux使用不可變狀態(tài),這意味著狀態(tài)是只讀的,不能直接去修改它,而是應(yīng)該返回一個(gè)新的狀態(tài),同時(shí)使用純函數(shù);mobx中的狀態(tài)是可變的,可以直接對(duì)其進(jìn)行修改
  • mobx相對(duì)來說比較簡單,在其中有很多的抽象,mobx更多的使用面向?qū)ο蟮木幊趟季S;redux會(huì)比較復(fù)雜,因?yàn)槠渲械暮瘮?shù)式編程思想掌握起來不是那么容易,同時(shí)需要借助一系列的中間件來處理異步和副作用
  • mobx中有更多的抽象和封裝,調(diào)試會(huì)比較困難,同時(shí)結(jié)果也難以預(yù)測(cè);而redux提供能夠進(jìn)行時(shí)間回溯的開發(fā)工具,同時(shí)其純函數(shù)以及更少的抽象,讓調(diào)試變得更加的容易

場(chǎng)景辨析:

基于以上區(qū)別,我們可以簡單得分析一下兩者的不同使用場(chǎng)景.

mobx更適合數(shù)據(jù)不復(fù)雜的應(yīng)用: mobx難以調(diào)試,很多狀態(tài)無法回溯,面對(duì)復(fù)雜度高的應(yīng)用時(shí),往往力不從心.

redux適合有回溯需求的應(yīng)用: 比如一個(gè)畫板應(yīng)用、一個(gè)表格應(yīng)用,很多時(shí)候需要撤銷、重做等操作,由于redux不可變的特性,天然支持這些操作.

mobx適合短平快的項(xiàng)目: mobx上手簡單,樣板代碼少,可以很大程度上提高開發(fā)效率.

當(dāng)然mobx和redux也并不一定是非此即彼的關(guān)系,你也可以在項(xiàng)目中用redux作為全局狀態(tài)管理,用mobx作為組件局部狀態(tài)管理器來用.

19。redux中如何進(jìn)行異步操作?

當(dāng)然,我們可以在componentDidmount中直接進(jìn)行請(qǐng)求無須借助redux.

但是在一定規(guī)模的項(xiàng)目中,上述方法很難進(jìn)行異步流的管理,通常情況下我們會(huì)借助redux的異步中間件進(jìn)行異步處理.

redux異步流中間件其實(shí)有很多,但是當(dāng)下主流的異步中間件只有兩種redux-thunk、redux-saga,當(dāng)然redux-observable可能也有資格占據(jù)一席之地,其余的異步中間件不管是社區(qū)活躍度還是npm下載量都比較差了.

20.redux異步中間件之間的優(yōu)劣?

redux-thunk優(yōu)點(diǎn):

  • 體積小: redux-thunk的實(shí)現(xiàn)方式很簡單,只有不到20行代碼
  • 使用簡單: redux-thunk沒有引入像redux-saga或者redux-observable額外的范式,上手簡單

redux-thunk缺陷:

  • 樣板代碼過多: 與redux本身一樣,通常一個(gè)請(qǐng)求需要大量的代碼,而且很多都是重復(fù)性質(zhì)的
  • 耦合嚴(yán)重: 異步操作與redux的action偶合在一起,不方便管理
  • 功能孱弱: 有一些實(shí)際開發(fā)中常用的功能需要自己進(jìn)行封裝

redux-saga優(yōu)點(diǎn):

  • 異步解耦: 異步操作被被轉(zhuǎn)移到單獨(dú) saga.js 中,不再是摻雜在 action.js 或 component.js 中
  • action擺脫thunk function: dispatch 的參數(shù)依然是一個(gè)純粹的 action (FSA),而不是充滿 “黑魔法” thunk function
  • 異常處理: 受益于 generator function 的 saga 實(shí)現(xiàn),代碼異常/請(qǐng)求失敗 都可以直接通過 try/catch 語法直接捕獲處理
  • 功能強(qiáng)大: redux-saga提供了大量的Saga 輔助函數(shù)和Effect 創(chuàng)建器供開發(fā)者使用,開發(fā)者無須封裝或者簡單封裝即可使用
  • 靈活: redux-saga可以將多個(gè)Saga可以串行/并行組合起來,形成一個(gè)非常實(shí)用的異步flow
  • 易測(cè)試,提供了各種case的測(cè)試方案,包括mock task,分支覆蓋等等

redux-saga缺陷:

  • 額外的學(xué)習(xí)成本: redux-saga不僅在使用難以理解的 generator function,而且有數(shù)十個(gè)API,學(xué)習(xí)成本遠(yuǎn)超redux-thunk,最重要的是你的額外學(xué)習(xí)成本是只服務(wù)于這個(gè)庫的,與redux-observable不同,redux-observable雖然也有額外學(xué)習(xí)成本但是背后是rxjs和一整套思想
  • 體積龐大: 體積略大,代碼近2000行,min版25KB左右
  • 功能過剩: 實(shí)際上并發(fā)控制等功能很難用到,但是我們依然需要引入這些代碼
  • ts支持不友好: yield無法返回TS類型

redux-observable優(yōu)點(diǎn):

  • 功能最強(qiáng): 由于背靠rxjs這個(gè)強(qiáng)大的響應(yīng)式編程的庫,借助rxjs的操作符,你可以幾乎做任何你能想到的異步處理
  • 背靠rxjs: 由于有rxjs的加持,如果你已經(jīng)學(xué)習(xí)了rxjs,redux-observable的學(xué)習(xí)成本并不高,而且隨著rxjs的升級(jí)redux-observable也會(huì)變得更強(qiáng)大

redux-observable缺陷:

  • 學(xué)習(xí)成本奇高: 如果你不會(huì)rxjs,則需要額外學(xué)習(xí)兩個(gè)復(fù)雜的庫
  • 社區(qū)一般: redux-observable的下載量只有redux-saga的1/5,社區(qū)也不夠活躍,在復(fù)雜異步流中間件這個(gè)層面redux-saga仍處于領(lǐng)導(dǎo)地位

21.什么是Virtual DOM

Virtual DOM是對(duì)DOM的抽象,本質(zhì)上是JavaScript對(duì)象,這個(gè)對(duì)象就是更加輕量級(jí)的對(duì)DOM的描述.

22.為什么需要Virtual DOM

既然我們已經(jīng)有了DOM,為什么還需要額外加一層抽象?

首先,我們都知道在前端性能優(yōu)化的一個(gè)秘訣就是盡可能少地操作DOM,不僅僅是DOM相對(duì)較慢,更因?yàn)轭l繁變動(dòng)DOM會(huì)造成瀏覽器的回流或者重回,這些都是性能的殺手,因此我們需要這一層抽象,在patch過程中盡可能地一次性將差異更新到DOM中,這樣保證了DOM不會(huì)出現(xiàn)性能很差的情況.

其次,現(xiàn)代前端框架的一個(gè)基本要求就是無須手動(dòng)操作DOM,一方面是因?yàn)槭謩?dòng)操作DOM無法保證程序性能,多人協(xié)作的項(xiàng)目中如果review不嚴(yán)格,可能會(huì)有開發(fā)者寫出性能較低的代碼,另一方面更重要的是省略手動(dòng)DOM操作可以大大提高開發(fā)效率.

最后,也是Virtual DOM最初的目的,就是更好的跨平臺(tái),比如Node.js就沒有DOM,如果想實(shí)現(xiàn)SSR(服務(wù)端渲染),那么一個(gè)方式就是借助Virtual DOM,因?yàn)閂irtual DOM本身是JavaScript對(duì)象.

23.組件設(shè)計(jì)原則

能否設(shè)計(jì)出通用前端組件也是區(qū)分前端工程師和前端api調(diào)用師的標(biāo)準(zhǔn)之一,那么應(yīng)該如何設(shè)計(jì)出一個(gè)通用組件呢?

細(xì)粒度的考量

我們?cè)趯W(xué)習(xí)設(shè)計(jì)模式的時(shí)候會(huì)遇到很多種設(shè)計(jì)原則,其中一個(gè)設(shè)計(jì)原則就是單一職責(zé)原則,在組件庫的開發(fā)中同樣適用,我們?cè)瓌t上一個(gè)組件只專注一件事情,單一職責(zé)的組件的好處很明顯,由于職責(zé)單一就可以最大可能性地復(fù)用組件,但是這也帶來一個(gè)問題,過度單一職責(zé)的組件也可能會(huì)導(dǎo)致過度抽象,造成組件庫的碎片化。

舉個(gè)例子,一個(gè)自動(dòng)完成組件(AutoComplete),他其實(shí)是由 Input 組件和 Select 組件組合而成的,因此我們完全可以復(fù)用之前的相關(guān)組件,就比如 Antd 的AutoComplete組件中就復(fù)用了Select組件,同時(shí)Calendar、 Form 等等一系列組件都復(fù)用了 Select 組件,那么Select 的細(xì)粒度就是合適的,因?yàn)?Select 保持的這種細(xì)粒度很容易被復(fù)用.

通用性考量

我們要設(shè)計(jì)的本身就是通用組件庫,不同于我們常見的業(yè)務(wù)組件,通用組件是與業(yè)務(wù)解耦但是又服務(wù)于業(yè)務(wù)開發(fā)的,那么問題來了,如何保證組件的通用性,通用性高一定是好事嗎?

組件的形態(tài)(DOM結(jié)構(gòu))永遠(yuǎn)是千變?nèi)f化的,但是其行為(邏輯)是固定的,因此通用組件的秘訣之一就是將 DOM 結(jié)構(gòu)的控制權(quán)交給開發(fā)者,組件只負(fù)責(zé)行為和最基本的 DOM 結(jié)構(gòu)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容