Vue 雙向綁定原理
mvvm 雙向綁定廷区,采用數(shù)據(jù)劫持結(jié)合發(fā)布者-訂閱者模式的方式列疗,通過 Object.defineProperty()
來劫持各個屬性的 setter、getter,在數(shù)據(jù)變動時發(fā)布消息給訂閱者应民,觸發(fā)相應(yīng)的監(jiān)聽回調(diào)。
幾個要點(diǎn):
1夕吻、實(shí)現(xiàn)一個數(shù)據(jù)監(jiān)聽器 Observer诲锹,能夠?qū)?shù)據(jù)對象的所有屬性進(jìn)行監(jiān)聽,如有變動可拿到最新值并通知訂閱者
2涉馅、實(shí)現(xiàn)一個指令解析器 Compile归园,對每個元素節(jié)點(diǎn)的指令進(jìn)行掃描和解析,根據(jù)指令模板替換數(shù)據(jù)稚矿,以及綁定相應(yīng)的更新函數(shù)
3庸诱、實(shí)現(xiàn)一個 Watcher,作為連接 Observer 和 Compile 的橋梁晤揣,能夠訂閱并收到每個屬性變動的通知桥爽,執(zhí)行指令綁定的相應(yīng)回調(diào)函數(shù),從而更新視圖
4昧识、mvvm 入口函數(shù)钠四,整合以上三者
具體步驟:
- 需要 observe 的數(shù)據(jù)對象進(jìn)行遞歸遍歷,包括子屬性對象的屬性滞诺,都加上 setter 和 getter
這樣的話形导,給這個對象的某個值賦值,就會觸發(fā) setter习霹,那么就能監(jiān)聽到了數(shù)據(jù)變化 - compile 解析模板指令朵耕,將模板中的變量替換成數(shù)據(jù),然后初始化渲染頁面視圖淋叶,并將每個指令對應(yīng)的節(jié)點(diǎn)綁定更新函數(shù)阎曹,添加監(jiān)聽數(shù)據(jù)的訂閱者,一旦數(shù)據(jù)有變動煞檩,收到通知处嫌,更新視圖
- Watcher 訂閱者是 Observer 和 Compile 之間通信的橋梁,主要做的事情是:
- 在自身實(shí)例化時往屬性訂閱器(dep)里面添加自己
- 自身必須有一個 update() 方法
- 待屬性變動 dep.notice() 通知時斟湃,能調(diào)用自身的 update() 方法熏迹,并觸發(fā) Compile 中綁定的回調(diào),則功成身退凝赛。
- MVVM 作為數(shù)據(jù)綁定的入口注暗,整合 Observer坛缕、Compile 和 Watcher 三者,通過Observer來監(jiān)聽自己的 model 數(shù)據(jù)變化捆昏,通過 Compile 來解析編譯模板指令赚楚,最終利用 Watcher 搭起 Observer 和 Compile 之間的通信橋梁,達(dá)到數(shù)據(jù)變化 -> 視圖更新骗卜;視圖交互變化(input) -> 數(shù)據(jù) model 變更的雙向綁定效果宠页。
描述下 vue 從初始化頁面--修改數(shù)據(jù)--刷新頁面 UI 的過程?
當(dāng) Vue 進(jìn)入初始化階段時寇仓,一方面 Vue 會遍歷 data 中的屬性举户,并用 Object.defineProperty 將它轉(zhuǎn)化成 getter/setter 的形式,實(shí)現(xiàn)數(shù)據(jù)劫持(暫不談 Vue3.0 的 Proxy)焚刺;另一方面敛摘,Vue 的指令編譯器 Compiler 對元素節(jié)點(diǎn)的各個指令進(jìn)行解析,初始化視圖乳愉,并訂閱 Watcher 來更新試圖兄淫,此時 Watcher 會將自己添加到消息訂閱器 Dep 中,此時初始化完畢蔓姚。
當(dāng)數(shù)據(jù)發(fā)生變化時捕虽,觸發(fā) Observer 中 setter 方法,立即調(diào)用 Dep.notify(),Dep 這個數(shù)組開始遍歷所有的訂閱者坡脐,并調(diào)用其 update 方法泄私,Vue 內(nèi)部再通過 diff 算法,patch 相應(yīng)的更新完成對訂閱者視圖的改變备闲。
你是如何理解 Vue 的響應(yīng)式系統(tǒng)的?
[圖片上傳失敗...(image-b4defe-1573527366002)]
響應(yīng)式系統(tǒng)簡述:
- 任何一個 Vue Component 都有一個與之對應(yīng)的 Watcher 實(shí)例
- Vue 的 data 上的屬性會被添加 getter 和 setter 屬性
- 當(dāng) Vue Component render 函數(shù)被執(zhí)行的時候, data 上會被 觸碰(touch), 即被讀, getter 方法會被調(diào)用, 此時 Vue 會去記錄此 Vue component 所依賴的所有 data晌端。(這一過程被稱為依賴收集)
- data 被改動時(主要是用戶操作), 即被寫, setter 方法會被調(diào)用, 此時 Vue 會去通知所有依賴于此 data 的組件去調(diào)用他們的 render 函數(shù)進(jìn)行更新
虛擬 DOM 實(shí)現(xiàn)原理
- 虛擬DOM本質(zhì)上是JavaScript對象,是對真實(shí)DOM的抽象
- 狀態(tài)變更時,記錄新樹和舊樹的差異
- 最后把差異更新到真正的dom中
詳細(xì)實(shí)現(xiàn)見 面試官: 你對虛擬DOM原理的理解?
既然 Vue 通過數(shù)據(jù)劫持可以精準(zhǔn)探測數(shù)據(jù)變化,為什么還需要虛擬 DOM 進(jìn)行 diff 檢測差異?
考點(diǎn): Vue 的變化偵測原理
前置知識: 依賴收集恬砂、虛擬 DOM咧纠、響應(yīng)式系統(tǒng)
現(xiàn)代前端框架有兩種方式偵測變化,一種是pull泻骤,一種是push
pull: 其代表為React漆羔,我們可以回憶一下React是如何偵測到變化的,我們通常會用setStateAPI顯式更新,然后React會進(jìn)行一層層的Virtual Dom Diff操作找出差異狱掂,然后Patch到DOM上演痒,React從一開始就不知道到底是哪發(fā)生了變化,只是知道「有變化了」趋惨,然后再進(jìn)行比較暴力的Diff操作查找「哪發(fā)生變化了」鸟顺,另外一個代表就是Angular的臟檢查操作。
push: Vue的響應(yīng)式系統(tǒng)則是push的代表器虾,當(dāng)Vue程序初始化的時候就會對數(shù)據(jù)data進(jìn)行依賴的收集诊沪,一但數(shù)據(jù)發(fā)生變化,響應(yīng)式系統(tǒng)就會立刻得知养筒。因此Vue是一開始就知道是「在哪發(fā)生變化了」,但是這又會產(chǎn)生一個問題端姚,如果你熟悉Vue的響應(yīng)式系統(tǒng)就知道,通常一個綁定一個數(shù)據(jù)就需要一個Watcher挤悉,一但我們的綁定細(xì)粒度過高就會產(chǎn)生大量的Watcher渐裸,這會帶來內(nèi)存以及依賴追蹤的開銷,而細(xì)粒度過低會無法精準(zhǔn)偵測變化,因此Vue的設(shè)計(jì)是選擇中等細(xì)粒度的方案,在組件級別進(jìn)行push偵測的方式,也就是那套響應(yīng)式系統(tǒng),通常我們會第一時間偵測到發(fā)生變化的組件,然后在組件內(nèi)部進(jìn)行Virtual Dom Diff獲取更加具體的差異装悲,而Virtual Dom Diff則是pull操作昏鹃,Vue是push+pull結(jié)合的方式進(jìn)行變化偵測的。
Vue 中 key 值的作用诀诊?
當(dāng) Vue.js 用 v-for 正在更新已渲染過的元素列表時洞渤,它默認(rèn)用“**就地復(fù)用**”策略。如果數(shù)據(jù)項(xiàng)的順序被改變属瓣,Vue 將不會移動 DOM 元素來匹配數(shù)據(jù)項(xiàng)的順序载迄, 而是簡單復(fù)用此處每個元素,并且確保它在特定索引下顯示已被渲染過的每個元素抡蛙。**key 的作用主要是為了高效的更新虛擬DOM**护昧。
Vue 的生命周期
-
beforeCreate
和created
-
beforeMount
和mounted
-
beforeUpdate
和updated
-
beforeDestory
和destoryed
-
activated
和deactivated
Vue 組件間通信有哪些方式?
- props/$emit
-
on
- vuex
-
listeners
- provide/inject
-
children 與 ref
watch、methods 和 computed 的區(qū)別?
- watch 為了監(jiān)聽某個響應(yīng)數(shù)據(jù)的變化粗截。computed 是自動監(jiān)聽依賴值的變化惋耙,從而動態(tài)返回內(nèi)容,主要目的是簡化模板內(nèi)的復(fù)雜運(yùn)算熊昌。所以區(qū)別來源于用法绽榛,只是需要動態(tài)值,那就用 computed 婿屹;需要知道值的改變后執(zhí)行業(yè)務(wù)邏輯灭美,才用 watch。
- methods是一個方法选泻,它可以接受參數(shù)冲粤,而computed 不能,computed 是可以緩存的页眯,methods 不會梯捕。computed 可以依賴其他 computed,甚至是其他組件的 data窝撵。
vue 中怎么重置 data?
使用Object.assign()傀顾,vm.options.data可以獲取到組件初始化狀態(tài)下的data碌奉。
Object.assign(this.$data, this.$options.data())
組件中寫 name 選項(xiàng)有什么作用短曾?
- 項(xiàng)目使用 keep-alive 時寒砖,可搭配組件 name 進(jìn)行緩存過濾
- DOM 做遞歸組件時需要調(diào)用自身 name
- vue-devtools 調(diào)試工具里顯示的組見名稱是由vue中組件name決定的
vue-router 有哪些鉤子函數(shù)?
官方文檔:vue-router鉤子函數(shù)
- 全局前置守衛(wèi)
router.beforeEach
- 全局解析守衛(wèi)
router.beforeResolve
- 全局后置鉤子
router.afterEach
- 路由獨(dú)享的守衛(wèi)
beforeEnter
- 組件內(nèi)的守衛(wèi)
beforeRouteEnter
、beforeRouteUpdate
嫉拐、beforeRouteLeave
前端路由簡介以及vue-router實(shí)現(xiàn)原理
route
和 router
的區(qū)別是什么哩都?
route
是“路由信息對象”,包括path
,params
,hash
,query
,fullPath
,matched
,name
等路由信息參數(shù)婉徘。
router
是“路由實(shí)例對象”漠嵌,包括了路由的跳轉(zhuǎn)方法(push
、replace
)盖呼,鉤子函數(shù)等儒鹿。
說一下 Vue 和 React 的認(rèn)識,做一個簡單的對比
1.監(jiān)聽數(shù)據(jù)變化的實(shí)現(xiàn)原理不同
Vue 通過 getter/setter 以及一些函數(shù)的劫持几晤,能精確快速的計(jì)算出 Virtual DOM 的差異约炎。這是由于它在渲染過程中,會跟蹤每一個組件的依賴關(guān)系蟹瘾,不需要重新渲染整個組件樹圾浅。
-
React 默認(rèn)是通過比較引用的方式進(jìn)行的,如果不優(yōu)化热芹,每當(dāng)應(yīng)用的狀態(tài)被改變時贱傀,全部子組件都會重新渲染,可能導(dǎo)致大量不必要的 VDOM 的重新渲染伊脓。
Vue 不需要特別的優(yōu)化就能達(dá)到很好的性能府寒,而對于 React 而言,需要通過 PureComponent/shouldComponentUpdate 這個生命周期方法來進(jìn)行控制报腔。如果你的應(yīng)用中株搔,交互復(fù)雜,需要處理大量的 UI 變化纯蛾,那么使用 Virtual DOM 是一個好主意纤房。如果你更新元素并不頻繁,那么 Virtual DOM 并不一定適用翻诉,性能很可能還不如直接操控 DOM炮姨。
為什么 React 不精確監(jiān)聽數(shù)據(jù)變化呢?這是因?yàn)?Vue 和 React 設(shè)計(jì)理念上的區(qū)別碰煌,Vue 使用的是可變數(shù)據(jù)舒岸,而 React 更強(qiáng)調(diào)數(shù)據(jù)的不可變。
2.數(shù)據(jù)流的不同
Vue 中默認(rèn)支持雙向綁定芦圾,組件與 DOM 之間可以通過 v-model 雙向綁定蛾派。但是,父子組件之間,props 在 2.x 版本是單向數(shù)據(jù)流
-
React 一直提倡的是單向數(shù)據(jù)流洪乍,他稱之為 onChange/setState()模式眯杏。
不過由于我們一般都會用 Vuex 以及 Redux 等單向數(shù)據(jù)流的狀態(tài)管理框架,因此很多時候我們感受不到這一點(diǎn)的區(qū)別了壳澳。
3.模板渲染方式的不同
在表層上岂贩,模板的語法不同
- React 是通過 JSX 渲染模板
- 而 Vue 是通過一種拓展的 HTML 語法進(jìn)行渲染
在深層上,模板的原理不同钾埂,這才是他們的本質(zhì)區(qū)別:
React 是在組件 JS 代碼中河闰,通過原生 JS 實(shí)現(xiàn)模板中的常見語法,比如插值褥紫,條件,循環(huán)等瞪慧,都是通過 JS 語法實(shí)現(xiàn)的
-
Vue 是在和組件 JS 代碼分離的單獨(dú)的模板中髓考,通過指令來實(shí)現(xiàn)的,比如條件語句就需要 v-if 來實(shí)現(xiàn)
對這一點(diǎn)弃酌,我個人比較喜歡 React 的做法氨菇,因?yàn)樗蛹兇飧釉椭欤?Vue 的做法顯得有些獨(dú)特徙歼,會把 HTML 弄得很亂亡电。舉個例子趾痘,說明 React 的好處:react 中 render 函數(shù)是支持閉包特性的翔试,所以我們 import 的組件在 render 中可以直接調(diào)用贝室。但是在 Vue 中哮幢,由于模板中使用的數(shù)據(jù)都必須掛在 this 上進(jìn)行一次中轉(zhuǎn)贬丛,所以我們 import 一個組件完了之后唬党,還需要在 components 中再聲明下鹃共,這樣顯然是很奇怪但又不得不這樣的做法。
Vue 的 nextTick 的原理是什么驶拱?
1. 為什么需要 nextTick
Vue 是異步修改 DOM 的并且不鼓勵開發(fā)者直接接觸 DOM霜浴,但有時候業(yè)務(wù)需要必須對數(shù)據(jù)更改--刷新后的 DOM 做相應(yīng)的處理,這時候就可以使用 Vue.nextTick(callback)這個 api 了蓝纲。
2. 理解原理前的準(zhǔn)備
首先需要知道事件循環(huán)中宏任務(wù)和微任務(wù)這兩個概念(這其實(shí)也是面試骋趺希考點(diǎn))。請閱大佬文章--徹底搞懂瀏覽器 Event-loop
常見的宏任務(wù)有 script, setTimeout, setInterval, setImmediate, I/O, UI rendering
常見的微任務(wù)有 process.nextTick(Nodejs),Promise.then(), MutationObserver;
3. 理解 nextTick
而 nextTick 的原理正是 vue 通過異步隊(duì)列控制 DOM 更新和 nextTick 回調(diào)函數(shù)先后執(zhí)行的方式税迷。如果大家看過這部分的源碼永丝,會發(fā)現(xiàn)其中做了很多 isNative()的判斷,因?yàn)檫@里還存在兼容性優(yōu)雅降級的問題翁狐±嘁纾可見 Vue 開發(fā)團(tuán)隊(duì)的深思熟慮,對性能的良苦用心。
如果你比較了解了前面的事件循環(huán)原理闯冷,推薦你看看這篇文章 請閱大佬文章--全面解析 Vue.nextTick 實(shí)現(xiàn)原理
Vuex 有哪幾種屬性?
有五種砂心,分別是 State
、Getter
蛇耀、Mutation
辩诞、Action
、Module
vue 首屏加載優(yōu)化
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 的引入刪掉了外永,不然我發(fā)現(xiàn)打包后的 app.css 還是會把 element 的 css 打包進(jìn)去,刪掉后就沒了拧咳。
然后你打包就會發(fā)現(xiàn) vendor 文件小了很多~
2. vue 路由的懶加載
import
或者require
懶加載伯顶。你打包就會發(fā)現(xiàn),多了很多 1.xxxxx.js骆膝;2.xxxxx.js 等等祭衩,而 vendor.xxx.js 沒了,剩下 app.js 和 manifest.js阅签,而且 app.js 還很小掐暮,我這里是 100k 多一點(diǎn)。
3. 不生成 map 文件
找到 config/index.js政钟,修改為 productionSourceMap: false
4. vue 組件盡量不要全局引入
5. 使用更輕量級的工具庫
6. 開啟gzip壓縮
這個優(yōu)化是兩方面的路克,前端將文件打包成.gz文件,然后通過nginx的配置锥涕,讓瀏覽器直接解析.gz文件衷戈。
7. 首頁單獨(dú)做服務(wù)端渲染
如果首頁真的有瓶頸,可以考慮用 node 單獨(dú)做服務(wù)端渲染层坠,而下面的子頁面仍用 spa 單頁的方式交互殖妇。
這里不推薦直接用 nuxt.js 服務(wù)端渲染方案,因?yàn)檫@樣一來增加了學(xué)習(xí)成本破花,二來服務(wù)端的維護(hù)成本也會上升谦趣,有時在本機(jī)測試沒問題,在服務(wù)端跑就有問題座每,為了省心前鹅,還是最大限度的使用靜態(tài)頁面較好。
參考鏈接:
vue首屏加載優(yōu)化
vue項(xiàng)目首屏加載優(yōu)化實(shí)戰(zhàn)
Vue 3.0 有沒有過了解峭梳?
關(guān)于Vue 3.0有幸看過尤大的關(guān)于3.0版本的[RFC Vue Function-based API RFC](https://zhuanlan.zhihu.com/p/68477600)舰绘。大致說了三個點(diǎn)蹂喻,第一個是關(guān)于提出的新API `setup()`函數(shù),第二個說了對于Typescript的支持捂寿,最后說了關(guān)于替換`Object.defineProperty`為 Proxy 的支持口四。
詳細(xì)說了下關(guān)于Proxy代替帶來的性能上的提升,因?yàn)閭鹘y(tǒng)的原型鏈攔截的方法秦陋,無法檢測對象及數(shù)組的一些更新操作蔓彩,但使用Proxy又帶來了瀏覽器兼容問題。
vue-cli 替我們做了哪些工作驳概?
首先需要知道 vue-cli 是什么赤嚼?它是基于 Vue.js 進(jìn)行快速開發(fā)的完整系統(tǒng),也可以理解成是很多 npm 包的集合顺又。其次更卒,vue-cli 完成的功能有哪些?
.vue 文件 --> .js 文件
ES6 語法 --> ES5 語法
Sass,Less,Stylus --> CSS
對 jpg,png,font 等靜態(tài)資源的處理
熱更新
定義環(huán)境變量稚照,區(qū)分 dev 和 production 模式
...
如果開發(fā)者需要補(bǔ)充或修改默認(rèn)設(shè)置逞壁,需要在 package.json 同級下新建一個 vue.config.js 文件