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 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原理的理解?
既然 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 中 key 值的作用?
當 Vue.js 用 v-for 正在更新已渲染過的元素列表時,它默認用“就地復用”策略。如果數據項的順序被改變,Vue 將不會移動 DOM 元素來匹配數據項的順序, 而是簡單復用此處每個元素,並且確保它在特定索引下顯示已被渲染過的每個元素。key 的作用主要是為了高效的更新虛擬DOM。
Vue 的生命周期
beforeCreate
和created
beforeMount
和mounted
beforeUpdate
和updated
beforeDestory
和destoryed
activated
和deactivated
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。
組件中寫 name 選項有什么作用?
- 項目使用 keep-alive 時,可搭配組件 name 進行緩存過濾
- DOM 做遞歸組件時需要調用自身 name
- vue-devtools 調試工具里顯示的組見名稱是由vue中組件name決定的
vue-router 有哪些鈎子函數?
官方文檔:vue-router鈎子函數
- 全局前置守衛
router.beforeEach
- 全局解析守衛
router.beforeResolve
- 全局后置鈎子
router.afterEach
- 路由獨享的守衛
beforeEnter
- 組件內的守衛
beforeRouteEnter
、beforeRouteUpdate
、beforeRouteLeave
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 的原理是什么?
1. 為什么需要 nextTick
Vue 是異步修改 DOM 的並且不鼓勵開發者直接接觸 DOM,但有時候業務需要必須對數據更改--刷新后的 DOM 做相應的處理,這時候就可以使用 Vue.nextTick(callback)這個 api 了。
2. 理解原理前的准備
首先需要知道事件循環中宏任務和微任務這兩個概念(這其實也是面試常考點)。請閱大佬文章--徹底搞懂瀏覽器 Event-loop
常見的宏任務有 script, setTimeout, setInterval, setImmediate, I/O, UI rendering
常見的微任務有 process.nextTick(Nodejs),Promise.then(), MutationObserver;
3. 理解 nextTick
而 nextTick 的原理正是 vue 通過異步隊列控制 DOM 更新和 nextTick 回調函數先后執行的方式。如果大家看過這部分的源碼,會發現其中做了很多 isNative()的判斷,因為這里還存在兼容性優雅降級的問題。可見 Vue 開發團隊的深思熟慮,對性能的良苦用心。
如果你比較了解了前面的事件循環原理,推薦你看看這篇文章 請閱大佬文章--全面解析 Vue.nextTick 實現原理
Vuex 有哪幾種屬性?
有五種,分別是 State
、Getter
、Mutation
、Action
、Module
vue 首屏加載優化
1. 把不常改變的庫放到 index.html 中,通過 cdn 引入
然后找到 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 文件小了很多~
2. vue 路由的懶加載
import
或者require
懶加載。你打包就會發現,多了很多 1.xxxxx.js;2.xxxxx.js 等等,而 vendor.xxx.js 沒了,剩下 app.js 和 manifest.js,而且 app.js 還很小,我這里是 100k 多一點。
3. 不生成 map 文件
找到 config/index.js,修改為 productionSourceMap: false
4. vue 組件盡量不要全局引入
5. 使用更輕量級的工具庫
6. 開啟gzip壓縮
這個優化是兩方面的,前端將文件打包成.gz文件,然后通過nginx的配置,讓瀏覽器直接解析.gz文件。
7. 首頁單獨做服務端渲染
如果首頁真的有瓶頸,可以考慮用 node 單獨做服務端渲染,而下面的子頁面仍用 spa 單頁的方式交互。
這里不推薦直接用 nuxt.js 服務端渲染方案,因為這樣一來增加了學習成本,二來服務端的維護成本也會上升,有時在本機測試沒問題,在服務端跑就有問題,為了省心,還是最大限度的使用靜態頁面較好。
參考鏈接:
vue首屏加載優化
vue項目首屏加載優化實戰
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 文件
更多vue面試題:
面試必備的13道可以舉一反三的Vue面試題
vue 248個知識點(面試題)為你保駕護航
2019前端面試系列——CSS面試題
2019前端面試系列——JS面試題
2019前端面試系列——JS高頻手寫代碼題
2019前端面試系列——Vue面試題
2019前端面試系列——HTTP、瀏覽器面試題