一次關(guān)于 Vue 的自我模擬面試

轉(zhuǎn)自:_楊溜溜
segmentfault.com/a/1190000023949535
前言
昨晚做了一個夢,夢見自己到了一家大廠面試,面試官走近房間,坐了下來:是楊溜溜吧?國際慣例,先來個自我介紹吧。于是我巴拉巴拉開始了長達兩分鐘的自我介紹,與此同時,面試官邊聽邊看我的簡歷,邊看邊皺眉,結(jié)束后問:看你之前的項目經(jīng)常用到Vue,對Vue熟悉嗎?我嘴角一笑,心里暗喜:幸好有專門看Vue的面試題,看來這次穩(wěn)了。于是謙虛又裝逼的回答:還行吧,您隨便問。于是面試官看我口氣那么大,心想:喲嚯,來了一個裝逼的,勞資今天就只問Vue。原文地址:https://github.com/yacan8/blog/issues/31
來,先介紹一下Vue的響應(yīng)式系統(tǒng)
Vue為MVVM框架,當(dāng)數(shù)據(jù)模型data變化時,頁面視圖會得到響應(yīng)更新,其原理對data的getter/setter方法進行攔截(Object.defineProperty或者Proxy),利用發(fā)布訂閱的設(shè)計模式,在getter方法中進行訂閱,在setter方法中發(fā)布通知,讓所有訂閱者完成響應(yīng)。在響應(yīng)式系統(tǒng)中,Vue會為數(shù)據(jù)模型data的每一個屬性新建一個訂閱中心作為發(fā)布者,而監(jiān)聽器watch、計算屬性computed、視圖渲染template/render三個角色同時作為訂閱者,對于監(jiān)聽器watch,會直接訂閱觀察監(jiān)聽的屬性,對于計算屬性computed和視圖渲染template/render,如果內(nèi)部執(zhí)行獲取了data的某個屬性,就會執(zhí)行該屬性的getter方法,然后自動完成對該屬性的訂閱,當(dāng)屬性被修改時,就會執(zhí)行該屬性的setter方法,從而完成該屬性的發(fā)布通知,通知所有訂閱者進行更新。
computed與watch的區(qū)別
計算屬性computed和監(jiān)聽器watch都可以觀察屬性的變化從而做出響應(yīng),不同的是:計算屬性computed更多是作為緩存功能的觀察者,它可以將一個或者多個data的屬性進行復(fù)雜的計算生成一個新的值,提供給渲染函數(shù)使用,當(dāng)依賴的屬性變化時,computed不會立即重新計算生成新的值,而是先標(biāo)記為臟數(shù)據(jù),當(dāng)下次computed被獲取時候,才會進行重新計算并返回。而監(jiān)聽器watch并不具備緩存性,監(jiān)聽器watch提供一個監(jiān)聽函數(shù),當(dāng)監(jiān)聽的屬性發(fā)生變化時,會立即執(zhí)行該函數(shù)。
介紹一下Vue的生命周期
beforeCreate:是new Vue()之后觸發(fā)的第一個鉤子,在當(dāng)前階段data、methods、computed以及watch上的數(shù)據(jù)和方法都不能被訪問。created:在實例創(chuàng)建完成后發(fā)生,當(dāng)前階段已經(jīng)完成了數(shù)據(jù)觀測,也就是可以使用數(shù)據(jù),更改數(shù)據(jù),在這里更改數(shù)據(jù)不會觸發(fā)updated函數(shù)。可以做一些初始數(shù)據(jù)的獲取,在當(dāng)前階段無法與Dom進行交互,如果非要想,可以通過vm.$nextTick來訪問Dom。beforeMount:發(fā)生在掛載之前,在這之前template模板已導(dǎo)入渲染函數(shù)編譯。而當(dāng)前階段虛擬Dom已經(jīng)創(chuàng)建完成,即將開始渲染。在此時也可以對數(shù)據(jù)進行更改,不會觸發(fā)updated。mounted:在掛載完成后發(fā)生,在當(dāng)前階段,真實的Dom掛載完畢,數(shù)據(jù)完成雙向綁定,可以訪問到Dom節(jié)點,使用$refs屬性對Dom進行操作。beforeUpdate:發(fā)生在更新之前,也就是響應(yīng)式數(shù)據(jù)發(fā)生更新,虛擬dom重新渲染之前被觸發(fā),你可以在當(dāng)前階段進行更改數(shù)據(jù),不會造成重渲染。updated:發(fā)生在更新完成之后,當(dāng)前階段組件Dom已完成更新。要注意的是避免在此期間更改數(shù)據(jù),因為這可能會導(dǎo)致無限循環(huán)的更新。beforeDestroy:發(fā)生在實例銷毀之前,在當(dāng)前階段實例完全可以被使用,我們可以在這時進行善后收尾工作,比如清除計時器。destroyed:發(fā)生在實例銷毀之后,這個時候只剩下了dom空殼。組件已被拆解,數(shù)據(jù)綁定被卸除,監(jiān)聽被移出,子實例也統(tǒng)統(tǒng)被銷毀。
為什么組件的data必須是一個函數(shù)
一個組件可能在很多地方使用,也就是會創(chuàng)建很多個實例,如果data是一個對象的話,對象是引用類型,一個實例修改了data會影響到其他實例,所以data必須使用函數(shù),為每一個實例創(chuàng)建一個屬于自己的data,使其同一個組件的不同實例互不影響。
組件之間是怎么通信的
父子組件通信
父組件 -> 子組件:prop子組件 -> 父組件:$on/$emit獲取組件實例:使用$parent/$children,$refs.xxx,獲取到實例后直接獲取屬性數(shù)據(jù)或調(diào)用組件方法
兄弟組件通信
Event Bus:每一個Vue實例都是一個Event Bus,都支持$on/$emit,可以為兄弟組件的實例之間new一個Vue實例,作為Event Bus進行通信。Vuex:將狀態(tài)和方法提取到Vuex,完成共享
跨級組件通信
使用provide/injectEvent Bus:同兄弟組件Event Bus通信Vuex:將狀態(tài)和方法提取到Vuex,完成共享
Vue事件綁定原理說一下
每一個Vue實例都是一個Event Bus,當(dāng)子組件被創(chuàng)建的時候,父組件將事件傳遞給子組件,子組件初始化的時候是有$on方法將事件注冊到內(nèi)部,在需要的時候使用$emit觸發(fā)函數(shù),而對于原生native事件,使用addEventListener綁定到真實的DOM元素上。
slot是什么?有什么作用?原理是什么?
slot又名插槽,是Vue的內(nèi)容分發(fā)機制,組件內(nèi)部的模板引擎使用slot元素作為承載分發(fā)內(nèi)容的出口。插槽slot是子組件的一個模板標(biāo)簽元素,而這一個標(biāo)簽元素是否顯示,以及怎么顯示是由父組件決定的。slot又分三類,默認(rèn)插槽,具名插槽和作用域插槽。
默認(rèn)插槽:又名匿名查抄,當(dāng)slot沒有指定name屬性值的時候一個默認(rèn)顯示插槽,一個組件內(nèi)只有有一個匿名插槽。 具名插槽:帶有具體名字的插槽,也就是帶有name屬性的slot,一個組件可以出現(xiàn)多個具名插槽。 作用域插槽:默認(rèn)插槽、具名插槽的一個變體,可以是匿名插槽,也可以是具名插槽,該插槽的不同點是在子組件渲染作用域插槽時,可以將子組件內(nèi)部的數(shù)據(jù)傳遞給父組件,讓父組件根據(jù)子組件的傳遞過來的數(shù)據(jù)決定如何渲染該插槽。
實現(xiàn)原理:當(dāng)子組件vm實例化時,獲取到父組件傳入的slot標(biāo)簽的內(nèi)容,存放在vm.$slot中,默認(rèn)插槽為vm.$slot.default,具名插槽為vm.$slot.xxx,xxx 為插槽名,當(dāng)組件執(zhí)行渲染函數(shù)時候,遇到slot標(biāo)簽,使用$slot中的內(nèi)容進行替換,此時可以為插槽傳遞數(shù)據(jù),若存在數(shù)據(jù),則可稱該插槽為作用域插槽。
Vue模板渲染的原理是什么?
vue中的模板template無法被瀏覽器解析并渲染,因為這不屬于瀏覽器的標(biāo)準(zhǔn),不是正確的HTML語法,所有需要將template轉(zhuǎn)化成一個JavaScript函數(shù),這樣瀏覽器就可以執(zhí)行這一個函數(shù)并渲染出對應(yīng)的HTML元素,就可以讓視圖跑起來了,這一個轉(zhuǎn)化的過程,就成為模板編譯。模板編譯又分三個階段,解析parse,優(yōu)化optimize,生成generate,最終生成可執(zhí)行函數(shù)render。
parse階段:使用大量的正則表達式對template字符串進行解析,將標(biāo)簽、指令、屬性等轉(zhuǎn)化為抽象語法樹AST。 optimize階段:遍歷AST,找到其中的一些靜態(tài)節(jié)點并進行標(biāo)記,方便在頁面重渲染的時候進行diff比較時,直接跳過這一些靜態(tài)節(jié)點,優(yōu)化runtime的性能。 generate階段:將最終的AST轉(zhuǎn)化為render函數(shù)字符串。
template預(yù)編譯是什么?
對于 Vue 組件來說,模板編譯只會在組件實例化的時候編譯一次,生成渲染函數(shù)之后在也不會進行編譯。因此,編譯對組件的 runtime 是一種性能損耗。而模板編譯的目的僅僅是將template轉(zhuǎn)化為render function,這個過程,正好可以在項目構(gòu)建的過程中完成,這樣可以讓實際組件在 runtime 時直接跳過模板渲染,進而提升性能,這個在項目構(gòu)建的編譯template的過程,就是預(yù)編譯。
那template和jsx的有什么分別?
對于 runtime 來說,只需要保證組件存在 render 函數(shù)即可,而我們有了預(yù)編譯之后,我們只需要保證構(gòu)建過程中生成 render 函數(shù)就可以。在 webpack 中,我們使用vue-loader編譯.vue文件,內(nèi)部依賴的vue-template-compiler模塊,在 webpack 構(gòu)建過程中,將template預(yù)編譯成 render 函數(shù)。與 react 類似,在添加了jsx的語法糖解析器babel-plugin-transform-vue-jsx之后,就可以直接手寫render函數(shù)。所以,template和jsx的都是render的一種表現(xiàn)形式,不同的是:JSX相對于template而言,具有更高的靈活性,在復(fù)雜的組件中,更具有優(yōu)勢,而 template 雖然顯得有些呆滯。但是 template 在代碼結(jié)構(gòu)上更符合視圖與邏輯分離的習(xí)慣,更簡單、更直觀、更好維護。
說一下什么是Virtual DOM
Virtual DOM 是 DOM 節(jié)點在 JavaScript 中的一種抽象數(shù)據(jù)結(jié)構(gòu),之所以需要虛擬DOM,是因為瀏覽器中操作DOM的代價比較昂貴,頻繁操作DOM會產(chǎn)生性能問題。虛擬DOM的作用是在每一次響應(yīng)式數(shù)據(jù)發(fā)生變化引起頁面重渲染時,Vue對比更新前后的虛擬DOM,匹配找出盡可能少的需要更新的真實DOM,從而達到提升性能的目的。
介紹一下Vue中的Diff算法
在新老虛擬DOM對比時
首先,對比節(jié)點本身,判斷是否為同一節(jié)點,如果不為相同節(jié)點,則刪除該節(jié)點重新創(chuàng)建節(jié)點進行替換 如果為相同節(jié)點,進行patchVnode,判斷如何對該節(jié)點的子節(jié)點進行處理,先判斷一方有子節(jié)點一方?jīng)]有子節(jié)點的情況(如果新的children沒有子節(jié)點,將舊的子節(jié)點移除) 比較如果都有子節(jié)點,則進行updateChildren,判斷如何對這些新老節(jié)點的子節(jié)點進行操作(diff核心)。 匹配時,找到相同的子節(jié)點,遞歸比較子節(jié)點
在diff中,只對同層的子節(jié)點進行比較,放棄跨級的節(jié)點比較,使得時間復(fù)雜從O(n^3)降低值O(n),也就是說,只有當(dāng)新舊children都為多個子節(jié)點時才需要用核心的Diff算法進行同層級比較。
key屬性的作用是什么
在對節(jié)點進行diff的過程中,判斷是否為相同節(jié)點的一個很重要的條件是key是否相等,如果是相同節(jié)點,則會盡可能的復(fù)用原有的DOM節(jié)點。所以key屬性是提供給框架在diff的時候使用的,而非開發(fā)者。
說說Vue2.0和Vue3.0有什么區(qū)別
重構(gòu)響應(yīng)式系統(tǒng),使用Proxy替換Object.defineProperty,使用Proxy優(yōu)勢:
可直接監(jiān)聽數(shù)組類型的數(shù)據(jù)變化 監(jiān)聽的目標(biāo)為對象本身,不需要像Object.defineProperty一樣遍歷每個屬性,有一定的性能提升 可攔截apply、ownKeys、has等13種方法,而Object.defineProperty不行 直接實現(xiàn)對象屬性的新增/刪除 新增Composition API,更好的邏輯復(fù)用和代碼組織
重構(gòu) Virtual DOM
模板編譯時的優(yōu)化,將一些靜態(tài)節(jié)點編譯成常量 slot優(yōu)化,將slot編譯為lazy函數(shù),將slot的渲染的決定權(quán)交給子組件 模板中內(nèi)聯(lián)事件的提取并重用(原本每次渲染都重新生成內(nèi)聯(lián)函數(shù)) 代碼結(jié)構(gòu)調(diào)整,更便于Tree shaking,使得體積更小
使用Typescript替換Flow
為什么要新增Composition API,它能解決什么問題
Vue2.0中,隨著功能的增加,組件變得越來越復(fù)雜,越來越難維護,而難以維護的根本原因是Vue的API設(shè)計迫使開發(fā)者使用watch,computed,methods選項組織代碼,而不是實際的業(yè)務(wù)邏輯。另外Vue2.0缺少一種較為簡潔的低成本的機制來完成邏輯復(fù)用,雖然可以minxis完成邏輯復(fù)用,但是當(dāng)mixin變多的時候,會使得難以找到對應(yīng)的data、computed或者method來源于哪個mixin,使得類型推斷難以進行。所以Composition API的出現(xiàn),主要是也是為了解決Option API帶來的問題,第一個是代碼組織問題,Compostion API可以讓開發(fā)者根據(jù)業(yè)務(wù)邏輯組織自己的代碼,讓代碼具備更好的可讀性和可擴展性,也就是說當(dāng)下一個開發(fā)者接觸這一段不是他自己寫的代碼時,他可以更好的利用代碼的組織反推出實際的業(yè)務(wù)邏輯,或者根據(jù)業(yè)務(wù)邏輯更好的理解代碼。第二個是實現(xiàn)代碼的邏輯提取與復(fù)用,當(dāng)然mixin也可以實現(xiàn)邏輯提取與復(fù)用,但是像前面所說的,多個mixin作用在同一個組件時,很難看出property是來源于哪個mixin,來源不清楚,另外,多個mixin的property存在變量命名沖突的風(fēng)險。而Composition API剛好解決了這兩個問題。
都說Composition API與React Hook很像,說說區(qū)別
從React Hook的實現(xiàn)角度看,React Hook是根據(jù)useState調(diào)用的順序來確定下一次重渲染時的state是來源于哪個useState,所以出現(xiàn)了以下限制
不能在循環(huán)、條件、嵌套函數(shù)中調(diào)用Hook 必須確保總是在你的React函數(shù)的頂層調(diào)用Hook useEffect、useMemo等函數(shù)必須手動確定依賴關(guān)系
而Composition API是基于Vue的響應(yīng)式系統(tǒng)實現(xiàn)的,與React Hook的相比
聲明在setup函數(shù)內(nèi),一次組件實例化只調(diào)用一次setup,而React Hook每次重渲染都需要調(diào)用Hook,使得React的GC比Vue更有壓力,性能也相對于Vue來說也較慢 Compositon API的調(diào)用不需要顧慮調(diào)用順序,也可以在循環(huán)、條件、嵌套函數(shù)中使用 響應(yīng)式系統(tǒng)自動實現(xiàn)了依賴收集,進而組件的部分的性能優(yōu)化由Vue內(nèi)部自己完成,而React Hook需要手動傳入依賴,而且必須必須保證依賴的順序,讓useEffect、useMemo等函數(shù)正確的捕獲依賴變量,否則會由于依賴不正確使得組件性能下降。
雖然Compositon API看起來比React Hook好用,但是其設(shè)計思想也是借鑒React Hook的。
SSR有了解嗎?原理是什么?
在客戶端請求服務(wù)器的時候,服務(wù)器到數(shù)據(jù)庫中獲取到相關(guān)的數(shù)據(jù),并且在服務(wù)器內(nèi)部將Vue組件渲染成HTML,并且將數(shù)據(jù)、HTML一并返回給客戶端,這個在服務(wù)器將數(shù)據(jù)和組件轉(zhuǎn)化為HTML的過程,叫做服務(wù)端渲染SSR。而當(dāng)客戶端拿到服務(wù)器渲染的HTML和數(shù)據(jù)之后,由于數(shù)據(jù)已經(jīng)有了,客戶端不需要再一次請求數(shù)據(jù),而只需要將數(shù)據(jù)同步到組件或者Vuex內(nèi)部即可。除了數(shù)據(jù)意外,HTML也結(jié)構(gòu)已經(jīng)有了,客戶端在渲染組件的時候,也只需要將HTML的DOM節(jié)點映射到Virtual DOM即可,不需要重新創(chuàng)建DOM節(jié)點,這個將數(shù)據(jù)和HTML同步的過程,又叫做客戶端激活。使用SSR的好處:
有利于SEO:其實就是有利于爬蟲來爬你的頁面,因為部分頁面爬蟲是不支持執(zhí)行JavaScript的,這種不支持執(zhí)行JavaScript的爬蟲抓取到的非SSR的頁面會是一個空的HTML頁面,而有了SSR以后,這些爬蟲就可以獲取到完整的HTML結(jié)構(gòu)的數(shù)據(jù),進而收錄到搜索引擎中。 白屏?xí)r間更短:相對于客戶端渲染,服務(wù)端渲染在瀏覽器請求URL之后已經(jīng)得到了一個帶有數(shù)據(jù)的HTML文本,瀏覽器只需要解析HTML,直接構(gòu)建DOM樹就可以。而客戶端渲染,需要先得到一個空的HTML頁面,這個時候頁面已經(jīng)進入白屏,之后還需要經(jīng)過加載并執(zhí)行 JavaScript、請求后端服務(wù)器獲取數(shù)據(jù)、JavaScript 渲染頁面幾個過程才可以看到最后的頁面。特別是在復(fù)雜應(yīng)用中,由于需要加載 JavaScript 腳本,越是復(fù)雜的應(yīng)用,需要加載的 JavaScript 腳本就越多、越大,這會導(dǎo)致應(yīng)用的首屏加載時間非常長,進而降低了體驗感。
更多詳情查看 徹底理解服務(wù)端渲染-SSR原理 https://github.com/yacan8/blog/issues/30
結(jié)束
面試官點了點頭,嗯呢,這小伙還可以,懂得還挺多,可以弄進來寫業(yè)務(wù)。我也暗自竊喜,幸虧沒問到我不會的,然后我坐那傻笑,笑著笑著,突然聽到我的鬧鈴響了,然后,我夢醒了。然后,新的搬磚的一天又開始了。
最后
歡迎加我微信(winty230),拉你進技術(shù)群,長期交流學(xué)習(xí)...
歡迎關(guān)注「前端Q」,認(rèn)真學(xué)前端,做個專業(yè)的技術(shù)人...


