2019前端面試系列——Vue面試題
目錄
Vue 雙向綁定原理
描述下 vue 從初始化頁面--修改數據--刷新頁面 UI 的過程?
你是如何理解 Vue 的響應式系統的?
虛擬 DOM 實現原理
既然 Vue 通過數據劫持可以精准探測數據變化,為什么還需要虛擬 DOM 進行 diff 檢測差異?
Vue 中 key 值的作用?
Vue 的生命周期
Vue 組件間通信有哪些方式?
watch、methods 和 computed 的區別?
vue 中怎么重置 data?
組件中寫 name 選項有什么作用?
vue-router 有哪些鈎子函數?
route 和 router 的區別是什么?
說一下 Vue 和 React 的認識,做一個簡單的對比
Vue 的 nextTick 的原理是什么?
Vuex 有哪幾種屬性?
vue 首屏加載優化
Vue 3.0 有沒有過了解?
vue-cli 替我們做了哪些工作?
Vue 雙向綁定原理
mvvm 雙向綁定,采用數據劫持結合發布者-訂閱者模式的方式,通過 Object.defineProperty() 來劫持各個屬性的 setter、getter,在數據變動時發布消息給訂閱者,觸發相應的監聽回調。
雙向綁定原理
幾個要點:
1、實現一個數據監聽器 Observer,能夠對數據對象的所有屬性進行監聽,如有變動可拿到最新值並通知訂閱者
2、實現一個指令解析器 Compile,對每個元素節點的指令進行掃描和解析,根據指令模板替換數據,以及綁定相應的更新函數
3、實現一個 Watcher,作為連接 Observer 和 Compile 的橋梁,能夠訂閱並收到每個屬性變動的通知,執行指令綁定的相應回調函數,從而更新視圖
4、mvvm 入口函數,整合以上三者
具體步驟:
需要 observe 的數據對象進行遞歸遍歷,包括子屬性對象的屬性,都加上 setter 和 getter
這樣的話,給這個對象的某個值賦值,就會觸發 setter,那么就能監聽到了數據變化
compile 解析模板指令,將模板中的變量替換成數據,然后初始化渲染頁面視圖,並將每個指令對應的節點綁定更新函數,添加監聽數據的訂閱者,一旦數據有變動,收到通知,更新視圖
Watcher 訂閱者是 Observer 和 Compile 之間通信的橋梁,主要做的事情是:
在自身實例化時往屬性訂閱器(dep)里面添加自己
自身必須有一個 update() 方法
待屬性變動 dep.notice() 通知時,能調用自身的 update() 方法,並觸發 Compile 中綁定的回調,則功成身退。
MVVM 作為數據綁定的入口,整合 Observer、Compile 和 Watcher 三者,通過Observer來監聽自己的 model 數據變化,通過 Compile 來解析編譯模板指令,最終利用 Watcher 搭起
Observer 和 Compile 之間的通信橋梁,達到數據變化 -> 視圖更新;視圖交互變化(input) -> 數據 model 變更的雙向綁定效果。
描述下 vue 從初始化頁面--修改數據--刷新頁面 UI 的過程?
當 Vue 進入初始化階段時,一方面 Vue 會遍歷 data 中的屬性,並用 Object.defineProperty 將它轉化成 getter/setter 的形式,實現數據劫持(暫不談 Vue3.0 的
Proxy);另一方面,Vue 的指令編譯器 Compiler 對元素節點的各個指令進行解析,初始化視圖,並訂閱 Watcher 來更新試圖,此時 Watcher 會將自己添加到消息訂閱器 Dep 中,此時初始化完畢。
當數據發生變化時,觸發 Observer 中 setter 方法,立即調用 Dep.notify(),Dep 這個數組開始遍歷所有的訂閱者,並調用其 update 方法,Vue 內部再通過 diff 算法,patch
相應的更新完成對訂閱者視圖的改變。
你是如何理解 Vue 的響應式系統的?
vue響應系統
響應式系統簡述:
任何一個 Vue Component 都有一個與之對應的 Watcher 實例
Vue 的 data 上的屬性會被添加 getter 和 setter 屬性
當 Vue Component render 函數被執行的時候, data 上會被 觸碰(touch), 即被讀, getter 方法會被調用, 此時 Vue 會去記錄此 Vue component 所依賴的所有
data。(這一過程被稱為依賴收集)
data 被改動時(主要是用戶操作), 即被寫, setter 方法會被調用, 此時 Vue 會去通知所有依賴於此 data 的組件去調用他們的 render 函數進行更新
虛擬 DOM 實現原理
虛擬DOM本質上是JavaScript對象,是對真實DOM的抽象
狀態變更時,記錄新樹和舊樹的差異
最后把差異更新到真正的dom中
詳細實現見 面試官: 你對虛擬DOM原理的理解?:https://user-gold-cdn.xitu.io/2019/8/1/16c49afec13e0416
既然 Vue 通過數據劫持可以精准探測數據變化,為什么還需要虛擬 DOM 進行 diff 檢測差異?
考點: Vue 的變化偵測原理
前置知識: 依賴收集、虛擬 DOM、響應式系統
現代前端框架有兩種方式偵測變化,一種是pull,一種是push
pull: 其代表為React,我們可以回憶一下React是如何偵測到變化的,我們通常會用setStateAPI顯式更新,然后React會進行一層層的Virtual Dom
Diff操作找出差異,然后Patch到DOM上,React從一開始就不知道到底是哪發生了變化,只是知道「有變化了」,然后再進行比較暴力的Diff操作查找「哪發生變化了」,另外一個代表就是Angular的臟檢查操作。
push:
Vue的響應式系統則是push的代表,當Vue程序初始化的時候就會對數據data進行依賴的收集,一但數據發生變化,響應式系統就會立刻得知。因此Vue是一開始就知道是「在哪發生變化了」,但是這又會產生一個問題,如果你熟悉Vue的響應式系統就知道,通常一個綁定一個數據就需要一個Watcher,一但我們的綁定細粒度過高就會產生大量的Watcher,這會帶來內存以及依賴追蹤的開銷,而細粒度過低會無法精准偵測變化,因此Vue的設計是選擇中等細粒度的方案,在組件級別進行push偵測的方式,也就是那套響應式系統,通常我們會第一時間偵測到發生變化的組件,然后在組件內部進行Virtual
Dom Diff獲取更加具體的差異,而Virtual Dom Diff則是pull操作,Vue是push+pull結合的方式進行變化偵測的。
Vue和React的視圖更新機制對比:https://blog.csdn.net/csdn_haow/article/details/89915908
Vue 中 key 值的作用?
當 Vue.js 用 v-for 正在更新已渲染過的元素列表時,它默認用“就地復用”策略。如果數據項的順序被改變,Vue 將不會移動 DOM 元素來匹配數據項的順序,
而是簡單復用此處每個元素,並且確保它在特定索引下顯示已被渲染過的每個元素。key 的作用主要是為了高效的更新虛擬DOM。
Vue 的生命周期
vue生命周期詳解:https://juejin.im/post/6844903780736040973
beforeCreate :初始化了部分參數,如果有相同的參數,做了參數合並,執行 beforeCreate ;
created :初始化了 Inject 、Provide 、 props 、methods 、data 、computed 和 watch,執行 created ;
beforeMount :檢查是否存在 el 屬性,存在的話進行渲染 dom 操作,執行 beforeMount ;
mounted :實例化 Watcher ,渲染 dom,執行 mounted ;
beforeUpdate :在渲染 dom 后,執行了 mounted 鈎子后,在數據更新的時候,執行 beforeUpdate ;
updated :檢查當前的 watcher 列表中,是否存在當前要更新數據的 watcher ,如果存在就執行 updated ;
beforeDestroy :檢查是否已經被卸載,如果已經被卸載,就直接 return 出去,否則執行 beforeDestroy ;
destroyed :把所有有關自己痕跡的地方,都給刪除掉;
Vue 組件間通信有哪些方式?
Vue 組件間通信六種方式
props/$emit
$emit/$on
vuex
$attrs/$listeners
provide/inject
$parent/$children 與 ref
watch、methods 和 computed 的區別?
watch 為了監聽某個響應數據的變化。computed 是自動監聽依賴值的變化,從而動態返回內容,主要目的是簡化模板內的復雜運算。所以區別來源於用法,只是需要動態值,那就用 computed ;需要知道值的改變后執行業務邏輯,才用
watch。
methods是一個方法,它可以接受參數,而computed 不能,computed 是可以緩存的,methods 不會。computed 可以依賴其他 computed,甚至是其他組件的 data。
vue 中怎么重置 data?
使用Object.assign(),vm.$data可以獲取當前狀態下的data,vm.$options.data(this)可以獲取到組件初始化狀態下的data。
Object.assign(this.$data, this.$options.data(this)) // 注意加this,不然取不到data() { a: this.methodA } 中的this.methodA。
原因參考:Vue中的this.$options.data()和this.$data
組件中寫 name 選項有什么作用?
項目使用 keep-alive 時,可搭配組件 name 進行緩存過濾
DOM 做遞歸組件時需要調用自身 name
vue-devtools 調試工具里顯示的組見名稱是由vue中組件name決定的
vue-router 有哪些鈎子函數?
官方文檔:vue-router鈎子函數
全局前置守衛 router.beforeEach
全局解析守衛 router.beforeResolve
全局后置鈎子 router.afterEach
路由獨享的守衛 beforeEnter
組件內的守衛 beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave
前端路由簡介以及vue-router實現原理:https://juejin.im/post/6844903615283363848
route 和 router 的區別是什么?
route是“路由信息對象”,包括path,params,hash,query,fullPath,matched,name等路由信息參數。
router是“路由實例對象”,包括了路由的跳轉方法(push、replace),鈎子函數等。
說一下 Vue 和 React 的認識,做一個簡單的對比
1.監聽數據變化的實現原理不同
Vue 通過 getter/setter 以及一些函數的劫持,能精確快速的計算出 Virtual DOM 的差異。這是由於它在渲染過程中,會跟蹤每一個組件的依賴關系,不需要重新渲染整個組件樹。
React 默認是通過比較引用的方式進行的,如果不優化,每當應用的狀態被改變時,全部子組件都會重新渲染,可能導致大量不必要的 VDOM 的重新渲染。
Vue 不需要特別的優化就能達到很好的性能,而對於 React 而言,需要通過 PureComponent/shouldComponentUpdate 這個生命周期方法來進行控制。如果你的應用中,交互復雜,需要處理大量的 UI
變化,那么使用 Virtual DOM 是一個好主意。如果你更新元素並不頻繁,那么 Virtual DOM 並不一定適用,性能很可能還不如直接操控 DOM。
為什么 React 不精確監聽數據變化呢?這是因為 Vue 和 React 設計理念上的區別,Vue 使用的是可變數據,而 React 更強調數據的不可變。
2.數據流的不同
Vue 中默認支持雙向綁定,組件與 DOM 之間可以通過 v-model 雙向綁定。但是,父子組件之間,props 在 2.x 版本是單向數據流
React 一直提倡的是單向數據流,他稱之為 onChange/setState()模式。
不過由於我們一般都會用 Vuex 以及 Redux 等單向數據流的狀態管理框架,因此很多時候我們感受不到這一點的區別了。
3.模板渲染方式的不同
在表層上,模板的語法不同
React 是通過 JSX 渲染模板
而 Vue 是通過一種拓展的 HTML 語法進行渲染
在深層上,模板的原理不同,這才是他們的本質區別:
React 是在組件 JS 代碼中,通過原生 JS 實現模板中的常見語法,比如插值,條件,循環等,都是通過 JS 語法實現的
Vue 是在和組件 JS 代碼分離的單獨的模板中,通過指令來實現的,比如條件語句就需要 v-if 來實現
對這一點,我個人比較喜歡 React 的做法,因為他更加純粹更加原生,而 Vue 的做法顯得有些獨特,會把 HTML 弄得很亂。舉個例子,說明 React 的好處:react 中 render 函數是支持閉包特性的,所以我們 import
的組件在 render 中可以直接調用。但是在 Vue 中,由於模板中使用的數據都必須掛在 this 上進行一次中轉,所以我們 import 一個組件完了之后,還需要在 components
中再聲明下,這樣顯然是很奇怪但又不得不這樣的做法。
Vue 的 nextTick 的原理是什么?
-
為什么需要 nextTick
Vue 是異步修改 DOM 的並且不鼓勵開發者直接接觸 DOM,但有時候業務需要必須對數據更改--刷新后的 DOM 做相應的處理,這時候就可以使用 Vue.nextTick(callback)這個 api 了。 -
理解原理前的准備
首先需要知道事件循環中宏任務和微任務這兩個概念(這其實也是面試常考點)。請閱大佬文章--徹底搞懂瀏覽器 Event-loop
常見的宏任務有 script, setTimeout, setInterval, setImmediate, I/O, UI rendering
常見的微任務有 process.nextTick(Nodejs),Promise.then(), MutationObserver; -
理解 nextTick
而 nextTick 的原理正是 vue 通過異步隊列控制 DOM 更新和 nextTick 回調函數先后執行的方式。如果大家看過這部分的源碼,會發現其中做了很多 isNative()的判斷,因為這里還存在兼容性優雅降級的問題。可見 Vue
開發團隊的深思熟慮,對性能的良苦用心。
如果你比較了解了前面的事件循環原理,推薦你看看這篇文章 請閱大佬文章--全面解析 Vue.nextTick 實現原理
Vuex 有哪幾種屬性?
有五種,分別是 State、Getter、Mutation、Action、Module
vue 首屏加載優化
- 把不常改變的庫放到 index.html 中,通過 cdn 引入
index.html
然后找到 build/webpack.base.conf.js 文件,在 module.exports = { } 中添加以下代碼
externals: {
'vue': 'Vue',
'vue-router': 'VueRouter',
'element-ui': 'ELEMENT',
},
這樣 webpack 就不會把 vue.js, vue-router, element-ui 庫打包了。聲明一下,我把 main.js 中對 element 的引入刪掉了,不然我發現打包后的 app.css 還是會把 element 的
css 打包進去,刪掉后就沒了。
然后你打包就會發現 vendor 文件小了很多~
-
vue 路由的懶加載
import或者require懶加載。你打包就會發現,多了很多 1.xxxxx.js;2.xxxxx.js 等等,而 vendor.xxx.js 沒了,剩下 app.js 和 manifest.js,而且 app.js 還很小,我這里是
100k 多一點。 -
不生成 map 文件
找到 config/index.js,修改為 productionSourceMap: false -
vue 組件盡量不要全局引入
-
使用更輕量級的工具庫
-
開啟gzip壓縮
這個優化是兩方面的,前端將文件打包成.gz文件,然后通過nginx的配置,讓瀏覽器直接解析.gz文件。 -
首頁單獨做服務端渲染
如果首頁真的有瓶頸,可以考慮用 node 單獨做服務端渲染,而下面的子頁面仍用 spa 單頁的方式交互。
這里不推薦直接用 nuxt.js 服務端渲染方案,因為這樣一來增加了學習成本,二來服務端的維護成本也會上升,有時在本機測試沒問題,在服務端跑就有問題,為了省心,還是最大限度的使用靜態頁面較好。
參考鏈接:
vue首屏加載優化:https://www.jianshu.com/p/df198914331b
vue項目首屏加載優化實戰:https://www.cnblogs.com/mianbaodaxia/p/10751453.html
Vue 3.0 有沒有過了解?
關於Vue 3.0有幸看過尤大的關於3.0版本的RFC Vue Function-based API RFC。大致說了三個點,第一個是關於提出的新API
setup()函數,第二個說了對於Typescript的支持,最后說了關於替換Object.defineProperty為 Proxy 的支持。
詳細說了下關於Proxy代替帶來的性能上的提升,因為傳統的原型鏈攔截的方法,無法檢測對象及數組的一些更新操作,但使用Proxy又帶來了瀏覽器兼容問題。
vue-cli 替我們做了哪些工作?
首先需要知道 vue-cli 是什么?它是基於 Vue.js 進行快速開發的完整系統,也可以理解成是很多 npm 包的集合。其次,vue-cli 完成的功能有哪些?
.vue 文件 --> .js 文件
ES6 語法 --> ES5 語法
Sass,Less,Stylus --> CSS
對 jpg,png,font 等靜態資源的處理
熱更新
定義環境變量,區分 dev 和 production 模式
...
如果開發者需要補充或修改默認設置,需要在 package.json 同級下新建一個 vue.config.js 文件