原文地址html
如今的前端頁面元素愈來愈多,結構也變得愈來愈複雜,當數據和視圖混合在一塊兒的時候對它們的處理會十分複雜,同時也很容易出現錯誤,而現代框架使用聲明式語法,描述組件對象的嵌套關係,並自動生成與dom對象的對應關係
參考1前端
vue生命週期 | 描述 |
---|---|
beforeCreate | 組件實力被建立,el和數據對象都爲undefined,還未初始化 |
create | 數據已經被初始化,而且初始化了Vue內部事件,可是DOM還未生成 |
befroeMount | 完成了模板的編譯。把data對象裏面的數據和vue的語法寫的模板編譯成了虛擬DOM |
mouted | 執行了render函數,將渲染出來的內容掛載到了DOM節點上 |
beforeUpdate | 組件更新以前:數據發生變化時,會調用beforeUpdate,而後經歷DOM diff |
updated | 組件更新後 |
actived | keep-alive 組件被激活 |
deactivated | keep-alive 移除 |
beforeDestroy | 組件銷燬前 |
destroyed | 組件銷燬後 |
能夠問數據變更如何和視圖聯繫在一塊兒?
Vue是採用數據劫持結合發佈者-訂閱者模式的方式, Vue相應系統有三大核心:observe
,dep
,watcher
; 精簡版Vue代碼參考
Observe
:當一個Vue實例建立時,initData階段,vue會遍歷data選項的屬性(observe),用 Object.defineProperty 將它們轉爲 getter/setter而且在內部追蹤相關依賴(dep),在屬性被訪問和修改時通知變化。Compite
:調用compile
方法解析模版,當視圖中有用到vue.data中數據的時候,會調用實例化watcher
方法進行依賴收集Watcher
:是Observer
和Compile
之間通訊的橋樑,當視圖中遇到綁定的數據時,在watcher
方法中會獲取這個數據,此時會觸發observe
中的getter
方法,Dep
:發佈訂閱模式,observe
中數據的getter
被觸發時會收集依賴的watcher
(dep.depend方法)observe
中數據的setter
,此時會調用dep.notify
方法給全部訂閱的watcher
發通知(經過回掉方式)進行視圖更新,此時會進行diff流程:
vue中的data爲對象,是引用類型,當重用組件時,一個組件對data作了更改,那麼另外一個組件也會跟着改,而使用返回一個函數返回數據,則每次返回都是一個新對象,引用地址不用,因此就不會出現問題
虛擬DOM是一個JavaScript對象,包含了當前DOM的基本結構和信息,它的存在是爲了減小對操做無用DOM所帶來的性能消耗,在大量的、頻繁的數據更新下可以對視圖進行合理的高效的更新(細粒度的精準修改),同時也抽象了原來的渲染過程,實現了跨平臺的能力vue
精簡源碼;當數據發生改變時,set
方法會讓調用Dep.notify
通知全部訂閱者Watcher
,訂閱者就會調用patch
給真實的DOM打補丁(兩個重要函數patchVnode
和updateChildren
):node
sameVnode
,若是不是的化,就會建立新的根結點並進行替換若是是sameVnode
,則進入patchVnode
函數,其基本判斷react
oldVnode === vnode
則直接return
新節點是文本節點
,則判斷新舊文本節點是否一致,不一致(oldVnode.text !== vnode.text
)則替換新節點不是文本節點
,則開始比較新舊節點的子節點oldCh
和ch
:子節點都存在
,則進行updateChildren
計算(稍後講)只有新子節點存在
,則若是舊節點有文本節點,則移除文本節點,而後將新子節點拆入只有舊子節點存在
,則移除全部子節點均無子節點且舊節點是文本節點
,則移除文本節點(此時新節點必定不是文本節點)updateChildren
函數作細緻對比git
idxInOld
)找到了idxInOld
,若是是相同節點
則移動舊節點到新的對應的地方,不然雖然key
相同但元素不一樣,看成新元素節點去建立沒有找到idxInOld
,則建立節點老節點先遍歷完
,則新節點比老節點多,將新節點多餘的插入進去新節點先遍歷完
,則就節點比新節點多,將舊節點多餘的刪除主要是爲了複用節點,高效的更新虛擬DOM,另外,在使用標籤元素過渡效果時也會用到keygithub
initComputed
,lazy: true
就會觸發Watcher中的watcher.dirty=true
(dirty決定了當前屬性是否更新),computed
引用的時候會第一次執行計算屬性,並將dirty
設置爲false
,並將結果保存在this.value
中進行緩存,computed
會這直接返回this.value
,只有當computed
所依賴的屬性發生變化時會將dirty
設置爲true
,並從新計算class Watcher{ …… evaluate () { this.value = this.get() this.dirty = false } …… } class initComputed{ …… //計算屬性的getter 獲取計算屬性的值時會調用 createComputedGetter (key) { return function computedGetter () { //獲取到相應的watcher const watcher = this._computedWatchers && this._computedWatchers[key] if (watcher) { //watcher.dirty 參數決定了計算屬性值是否須要從新計算,默認值爲true,即第一次時會調用一次 if (watcher.dirty) { /*每次執行以後watcher.dirty會設置爲false,只要依賴的data值改變時纔會觸發 watcher.dirty爲true,從而獲取值時重新計算*/ watcher.evaluate() } //獲取依賴 if (Dep.target) { watcher.depend() } //返回計算屬性的值 return watcher.value } } } …… }
計算屬性
顧名思義就是經過其餘變量計算得來的,它的值是基於其所依賴的屬性來進行緩存
的,只有在其所依賴的屬性發生變化時纔會重新求值 watch
是監聽一個變量,當變量發生變化時,會調用對應的方法算法
vue實現響應式並非數據一更新就馬上觸發dom變化,而是按照必定的策略對dom進行更新,源碼位置,原理:vue-router
callbacks
數組中,$nextTick
沒有傳cb
回掉,則返回一個promise
接下來就是callbacks
的執行時機數組
promise
,則用promise.resolve().then
來執行callbacks
MutationObserver
,則用實例化的MutationObserver
監聽文本變化來執行回掉,setImmediate
,則用setImmediate(cb)
來執行回掉setTimeout(fn,0)
來執行vue2.5.X
版本中對於像v-on
這樣的DOM交互事件,默認走macroTimerFunc
,也就是,跳過第一步promise
的判斷,initProps
時,會對props進行defineReactive操做,傳入的第四個參數是自定義的set報錯判斷函數,該函數會在觸發props的set方法時執行// src/core/instance/state.js 源碼路徑 function initProps (vm: Component, propsOptions: Object) { ... for (const key in propsOptions) { if (process.env.NODE_ENV !== 'production') { ... defineReactive(props, key, value, () => { // 若是不是跟元素而且不是更新子元素 if (!isRoot && !isUpdatingChildComponent) { warn( `Avoid mutating a prop directly since the value will be ` + `overwritten whenever the parent component re-renders. ` + `Instead, use a data or computed property based on the prop's ` + `value. Prop being mutated: "${key}"`, vm ) } })} ... } } // src/core/observer/index.js export function defineReactive (obj,key,val,customSetter,shallow) { const property = Object.getOwnPropertyDescriptor(obj, key) const getter = property && property.get const setter = property && property.set Object.defineProperty(obj, key, { ... set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val if (newVal === value || (newVal !== newVal && value !== value)) { return } if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() } if (getter && !setter) return if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) dep.notify() } }) }
加載過程:父組件beforeCreate => 父組件created => 父組件beforeMount => 子組件beforeCreate => 子組件created => 子組件 beforeMount => 子組件mounted => 父組件mounted
更新過程:父組件beforeUpdate => 子組件beforeUpdate => 子組件updated => 父組件updated
銷燬過程:父組件beforeDestroy => 子組件 beforeDestroy => 子組件 destoryed => 父組件 destoryed
beforeEach
守衛。beforeRouteUpdate
守衛 (2.2+)。beforeEnter
。beforeRouteEnter
。beforeResolve
守衛 (2.5+)。afterEach
鉤子。beforeRouteEnter
守衛中傳給 next 的回調函數。router.beforeResolve
註冊一個全局守衛。這和 router.beforeEach
相似,區別是在導航被確認以前,同時在全部組件內守衛和異步路由組件被解析以後,解析守衛就被調用