***************** 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)出同步
- setState 只在合成事件和鉤子函數(shù)中是“異步”的,在原生事件和setTimeout 中都是同步的。
- setState 的“異步”并不是說內(nèi)部由異步代碼實(shí)現(xiàn),其實(shí)本身執(zhí)行的過程和代碼都是同步的,只是合成事件和鉤子函數(shù)的調(diào)用順序在更新之前,導(dǎo)致在合成事件和鉤子函數(shù)中沒法立馬拿到更新后的值,形成了所謂的“異步”,當(dāng)然可以通過第二個(gè)參數(shù) setState(partialState, callback) 中的callback拿到更新后的結(jié)果。
- 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è)工作流程:
- 首先,用戶(通過View)發(fā)出Action,發(fā)出方式就用到了dispatch方法。
- 然后,Store自動(dòng)調(diào)用Reducer,并且傳入兩個(gè)參數(shù):當(dāng)前State和收到的Action,Reducer會(huì)返回新的State
- 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)