vue是一套用於構建用戶界面的漸進式框架。與其它大型框架不同的是,Vue 被設計為可以自底向上逐層應用。Vue 的核心庫只關注視圖層,不僅易於上手,還便於與第三方庫或既有項目整合。
vue是一個MVVM框架,MVVM是一個MVC框架的改進版,由model-view-viewModel三塊組成,由viewModel來是實現數據(model)和視圖(view)之間的通信。
生命周期
生命周期中有多個事件鈎子,讓我們在控制整個Vue實例的過程時更容易形成好的邏輯。
beforeCreate、created、beforeMounted、mounted、beforeUpdate、updated、beforeDestroy、destroyed。
底層實現原理
vue是一個MVVM的單向數據流,數據雙向綁定的框架,2.0的雙向數據綁定是由Object.defineProperty來實現,3.0是由Object.proxy來實現。
具體實現流程:
- Observer:核心是通過Obeject.defineProperty()來監聽數據的變動,這個函數內部可以定義setter和getter,每當數據發生變化,就會觸發setter,進行數據劫持,然后通知訂閱者,訂閱者就是Watcher。
-
Watcher:訂閱者作為Observer和Compile之間通信的橋梁。在收到屬性變化的通知后,調用自身的更新函數,觸發Compile中綁定的回調函數。
-
Compile:主要做的事情是解析模板指令,將模板中的變量替換成數據,然后初始化渲染頁面視圖,並將每個指令對應的節點綁定更新函數,添加監聽數據的訂閱者,一旦數據有變動,收到通知,更新視圖。
虛擬 DOM
虛擬DOM本質上就是一個普通的JavaScript對象,里面能很好的表示DOM元素需要記錄的信息:節點名稱、屬性,文本,子節點等。比如下:
{ // 節點類型 type: 'ul', // 節點的屬性,包括dom原生屬性和自定義屬性 props: { class: 'list', style: 'color:red;' }, // 子節點數組 // 子對象結構也是一樣,包含了type,props,children,沒有子節點的話就是普通文本 // 子對象擁有子節點時,繼續往下擴展就行 children: [ {type: 'li',props: {class: 'list'},children: ['利群']}, {type: 'li',props: {class: 'list'},children: ['玉溪']}, {type: 'li',props: {class: 'list'},children: ['黃鶴樓']} ] }
虛擬DOM的必要性:
virtual-dom
(后文簡稱vdom
)的概念大規模的推廣還是得益於react
出現,virtual-dom
也是react
這個框架的非常重要的特性之一。相比於頻繁的手動去操作dom
而帶來性能問題,vdom
很好的將dom
做了一層映射關系,進而將在我們本需要直接進行dom
的一系列操作,映射到了操作vdom
,而vdom
上定義了關於真實dom
的一些關鍵的信息,vdom
完全是用js
去實現,和宿主瀏覽器沒有任何聯系,此外得益於js
的執行速度,將原本需要在真實dom
進行的創建節點
,刪除節點
,添加節點
等一系列復雜的dom
操作全部放到vdom
中進行,這樣就通過操作vdom
來提高直接操作的dom
的效率和性能。
Virtual DOM 算法實現的大致邏輯:
- 用JavaScript對象結構DOM樹的結構,然后用這個結構構建成一個真正的DOM樹,並渲染到文檔中去。
- 當JavaScript對象發生變化,則重新構建一顆新的對象樹,然后比較兩個對象樹,記錄兩棵樹之間的差別。
- 把記錄的差別應用到步驟1所構建的DOM樹上,完成更新。
數據響應式設計的必要性
所謂數據響應式就是用戶對數據層做的更改能夠觸發視圖層做出的更新響應的機制。
mvvm 框架中要解決的一個核心問題是連接數據層和視圖層,通過數據驅動應用,數據變化,視圖更新,要做到這點的就需要實現一套響應式機制,這樣一旦數據發生變化就可以立即做出更新處理。
在 vue 中,通過數據響應式加上虛擬 DOM 和 patch 算法,可以使我們只需要操作數據,完全不用接觸繁瑣的 DOM 操作,從而大大提升開發效率,降低開發難度。
vue2 中的數據響應式會根據數據類型來做不同處理,如果是對象則采用 Object.defineProperty() 的方式定義數據攔截,當數據被訪問或發生變化時,框架感知並作出相應;如果是數組則通過覆蓋該數組原型的方法,擴展它的7個變更方法,使這些方法可以額外的做出更新通知,從而作出相應。這種機制很好的解決了數據響應化的問題,但也存在一些缺點:比如初始化時的遞歸遍歷會造成性能損失;新增或刪除時需要用戶使用 Vue.set/delete 這樣特殊的 api 才能生效;對於 es6 中新產生的 Map、Set 等數據結構不支持等問題。
基於這些問題,vue3 重新編寫了這一部分的實現:利用 es6 的 Proxy 機制代理要相應化的數據,達到變成體驗一致,不需要使用特殊 api ,初始化新能和內存消耗都得到了大幅改善;另外由於響應化的實現代碼抽取為獨立的 reactivity 包,我們可以更靈活的使用它,甚至不要 vue 的引入。
watcher與computed的區別
watcher:一個值影響多個值;監聽已有屬性;允許異步操作
computed:多個值影響一個值;生成一個新的屬性;不允許異步操作;必須有依賴型數據;當依賴數據發生變化才會重新計算,否則用緩存;內部有getter和setter方法。
created與mounted的區別
主要的區別在於是否有渲染DOM樹。
- beforeCreate:DOM節點、data、methods都不能獲取。
- created:DOM節點不能獲取,data、methods可以獲取。
- beforeMounted:DOM節點不能獲取,data、methods可以獲取。
- mounted:DOM節點、data、methods都可以獲取。
v-for與v-if為什么避免一起使用
v-for的優先級高於v-if,所以v-if會執行在每一個v-for的子元素中,從而降低性能。
解決方法:
- 將v-if置於v-for的上層
- 將需要循環的屬性通過computed進行過濾,然后v-for循環computed屬性。
循環指令中,key值的作用
主要作用是用來提高虛擬DOM更新的效率,是在diff算法判斷中,用來判斷兩個節點是否相同的一個很重要的標准。
組件間的通信
- 父傳子props,v-model,子傳父emit
- 全局vuex,eventBus(Vue.prototype.bus = new Vue())
vue.use的用途及原理
use方法是用來擴展插件的。源碼如下(src/core/global-api/use.js):
export function initUse (Vue: GlobalAPI) { Vue.use = function (plugin: Function | Object) { const installedPlugins = (this._installedPlugins || (this._installedPlugins = [])) // 如果當前plugin已經被use調用過了,則直接返回 if (installedPlugins.indexOf(plugin) > -1) { return this } // additional parameters const args = toArray(arguments, 1) args.unshift(this) // 如果plugin對象中有install方法,則執行 if (typeof plugin.install === 'function') { plugin.install.apply(plugin, args) // 如果plugin本身就是方法,則執行 } else if (typeof plugin === 'function') { plugin.apply(null, args) } // 最后再將plugin緩存起來 installedPlugins.push(plugin) return this } }
vue.$nextTick方法的用途及原理
nextTick 是 vue 提供的一個全局 api ,由於 vue 的異步更新策略導致我們對數據的修改不會立刻體現在 DOM 變化上,如果想要立即獲取更新后的 DOM 狀態, 就需要使用這個方法。
vue 在更新 DOM 時是異步執行的。只要偵聽到數據變化,vue 就會開啟一個隊列,並緩沖在同一事件循環中發生的所有數據變更。如果同一個 watcher 被多次觸發,只會推入到隊列中一次。這種在緩存時去除重復數據對於避免不必要的計算和 DOM 操作是非常重要的。nextTick 方法會在隊列中加入一個回調函數,確保該函數在前面的 DOM 操作完成后才調用。
以下為vue源碼(src/core/util/next-tick.js):
let timerFunc // 如果有Promise且是原生API,則選用Promise(微任務) if (typeof Promise !== 'undefined' && isNative(Promise)) { const p = Promise.resolve() timerFunc = () => { p.then(flushCallbacks) if (isIOS) setTimeout(noop) } isUsingMicroTask = true // 如果支持MutationObserver,則用MutationObserver(MutationObserver是用來監聽DOM變化的API,為微任務) } else if (!isIE && typeof MutationObserver !== 'undefined' && ( isNative(MutationObserver) || // PhantomJS and iOS 7.x MutationObserver.toString() === '[object MutationObserverConstructor]' )) { let counter = 1 const observer = new MutationObserver(flushCallbacks) const textNode = document.createTextNode(String(counter)) observer.observe(textNode, { characterData: true }) timerFunc = () => { counter = (counter + 1) % 2 textNode.data = String(counter) } isUsingMicroTask = true // 如果支持setTmmediate,則用setTmmediate(可以看成setTimeout,兼容性不好,僅IE10+及nodejs支持,為宏任務) } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { timerFunc = () => { setImmediate(flushCallbacks) } } else { // Fallback to setTimeout. // 最后的應急方案,setTimeout方法(宏任務) timerFunc = () => { setTimeout(flushCallbacks, 0) } }
上面代碼分析得出,在vue被實例化執行的時候,首先判斷微任務(Promise > MutationObserver)是否支持,然后再判斷宏任務(setImmediate)是否支持,最后采用setTimeout方法。具體之間的區別可以查下JavaScript的事件循環,微任務與宏任務的區別。
mixin
Mixins 使我們能夠為 Vue 組件編寫可插拔和可重用的功能。如果我們希望在多個組件間重用一組選項,比如data、computed、methods等,那么我們可以將其寫成mixin,並在需要的地方引用它,然后通過mixin合並到當前組件。如果一個組件中有引入mixin,那么mixin中的生命周期hook將優先組件自己內的hook。
vuex
Vuex 是一個專為 Vue.js 應用程序開發的響應式狀態管理模式。它采用集中式存儲管理應用的所有組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。
規則:
- 應用層級的狀態應該集中到單個 store 對象中。
- 提交 mutation 是更改狀態的唯一方法,並且這個過程是同步的。
- 異步邏輯都應該封裝到 action 里面。
5個核心概念:
- state:單一的狀態樹對象,包含了全部的應用層級狀態。可以簡單的理解為整個狀態管理的倉庫地址,可以設置內部所有屬性的初始值,並且整個store狀態是響應式的,每當state內的屬性值改變,監視的vue組件也會同步更新。
- mutation:提交 mutation是更改 Vuex 的 store 中的狀態的唯一方法,它是一個同步函數。
- action:類似與mutation,不同的是action不能直接更改state,要通過提交mutation來更改。在action內可以執行任何異步任務,也可以通過dispath調用另一個action任務。
- module:當項目很復雜的時候,我們可以使用module來進行模塊划分。每個module內部都有自己的state,mutation,action,getter、對於模塊內部的 mutation 和 getter,接收的第一個參數是模塊的局部狀態對象、action內部使用的根節點為context.rootState、getter使用的根節點為第三個參數。默認情況下,模塊內部的state是注冊在全局命名空間的,如果有使用namespaced為true,那么在引用的時候,需要在前面加上模塊名稱,如(this.$store.dispath('moduleName/actionFunction()'))。
- getter:可以認為為store的計算屬性,通過它可以對state狀態的值進行重新計算緩存並返回。
數據傳輸流程:
- 在實例化Vuex.Store()的時候,創建一個全局倉庫state,並將內部屬性進行數據劫持。
- state內部數據需要修改的時候需要遵循單項數據流,在組件中通過dispath方法調用action方法。
- action內部方法處理完成后,然后再調用commit方法通知mutation。
- mutation內部方法用來修改state中的數據。
- state內部數據更改完成,因其數據為響應式,則組件中依賴的值也會相應變化
v-model原理
v-model 是 vueJs 用來實現數據雙向綁定的功能,其實質是一個語法糖,既綁定了數據,又添加了一個 input 時間監聽:
// 正常寫法 <Children v-model="number" /> // 等同於 <Children :value="number" @input="number = $event" /> // Children.vue <template> <div @click="$emit('input', value + 1)"></div> </template> <script> export default { props: ['value'] } </script>
未完待續......