入口文件
vue-2.6.11\src\platforms\web\entry-runtime-with-compiler.js
-
從vue原型中拿出
$mount
進(jìn)行覆蓋凶朗,// 17行 const mount = Vue.prototype.$mount
-
解析模板相關(guān)選項(xiàng)
// 18行 Vue.prototype.$mount = function ( el?: string | Element,// 宿主元素 hydrating?: boolean ): Component { //獲取真實(shí)DOM el = el && query(el) // 34行 // 先判斷有沒有render远荠,render函數(shù)優(yōu)先級最高 // 優(yōu)先級:render > template > el if (!options.render) { let template = options.template // 再判斷有沒有template并解析 if (template) { if (typeof template === 'string') { if (template.charAt(0) === '#') { template = idToTemplate(template) /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && !template) { } } } else if (template.nodeType) { template = template.innerHTML } else { return this } // 最后查看el選項(xiàng) } else if (el) { template = getOuterHTML(el) } // 處理模板的方式砸抛,編譯它亚铁,最終目標(biāo)是render if (template) { /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile') } const { render, staticRenderFns } = compileToFunctions(template, { outputSourceRange: process.env.NODE_ENV !== 'production', shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this) // 重新復(fù)制給選項(xiàng) options.render = render options.staticRenderFns = staticRenderFns /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile end') measure(`vue ${this._name} compile`, 'compile', 'compile end') } } }
初始化掛載
vue-2.6.11\src\platforms\web\runtime\index.js
-
安裝平臺特有的補(bǔ)丁(patch)函數(shù):做初始化和更新蝇刀,為了實(shí)現(xiàn)跨平臺,平臺特有的操作徘溢。
// 33行 Vue.prototype.__patch__ = inBrowser ? patch : noop
-
實(shí)現(xiàn)
$mount
:初始化掛載吞琐。$mount('#app')=>mountComponent:render()=>vdom=>patch()=>dom=>appendChile()
- 初始化時執(zhí)行
$mount('#app')
,在內(nèi)部調(diào)用mountComponent
然爆,調(diào)用內(nèi)部的render
函數(shù)顽分,核心目標(biāo)把當(dāng)前虛擬DOM樹傳給patch()
函數(shù)變成真實(shí)DOM,真實(shí)DOM掛載到#app
上施蜜。
// 36行 Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && inBrowser ? query(el) : undefined return mountComponent(this, el, hydrating) }
初始化全局API
vue-2.6.11\src\core\index.js
-
初始化全局API:
Vue.use
,component
,directive
,filter
,mixin
,set
,extend
,delete
// 6行 initGlobalAPI(Vue)
Vue的構(gòu)造函數(shù)
vue-2.6.11\src\core\instance\index.js
-
構(gòu)造函數(shù):new Vue(options)
// 8行 function Vue (options) { // 初始化 this._init(options) }
-
聲明實(shí)例屬性和方法卒蘸,把構(gòu)造函數(shù)傳遞進(jìn)去,通過混入模式把原型加上
_init
方法缸沃。initMixin(Vue) // 熟悉的其他梳理屬性和方法,有下面這些混入 stateMixin(Vue) eventsMixin(Vue) lifecycleMixin(Vue) renderMixin(Vue)
初始化
vue-2.6.11\src\core\instance\init.js
-
原型掛載
_init
實(shí)現(xiàn)原型方法翘单,在任何實(shí)例都可使用vm._init
Vue.prototype._init = function (options?: Object)
-
合并選項(xiàng)
// 30行 // 合并選項(xiàng):new Vue傳入的時用戶配置貌亭,需要和系統(tǒng)配置合并 if (options && options._isComponent) { initInternalComponent(vm, options) } else { vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) }
-
初始化操作
// 52行 // 生命周期初始化 組件實(shí)例相關(guān)的屬性初始化,$parent,$root,$children,$refs initLifecycle(vm) // 事件初始化 監(jiān)聽自定義事件 initEvents(vm) // 解析插槽剧腻,$slots,$scopeSlots,$createElement() initRender(vm) callHook(vm, 'beforeCreate')// beforeCreate生命周期 // 組件狀態(tài)相關(guān)的數(shù)據(jù)操作 // inject/provide 注入祖輩傳遞下來的數(shù)據(jù)。 initInjections(vm) // // 把自己內(nèi)部的狀態(tài)進(jìn)行響應(yīng)式的處理蕊温,`props`,`methods`,`data`,`computed`,`watch` initState(vm) // 提供給后代,用來隔代傳遞參數(shù) initProvide(vm) callHook(vm, 'created')// created生命周期
-
initEvents(vm)
- 為什么要從父組件找Listeners凉翻?
- 子組件上有事件選項(xiàng)制轰,但是在父組件內(nèi)聲明的。
- 派發(fā)和監(jiān)聽者都是子組件本身
(this.$on(),this.$emit())
; - 事件的真正監(jiān)聽者時子組件自己调俘。
-
updateComponentListeners
從父組件身上拿出事件給子組件。
export function initEvents (vm: Component) { vm._events = Object.create(null) vm._hasHookEvent = false // 找到父組件的選項(xiàng) const listeners = vm.$options._parentListeners if (listeners) { updateComponentListeners(vm, listeners) } }
- 為什么要從父組件找Listeners凉翻?
-
initRender(vm)
// 34行 // 這里的$createElement就是render(h) vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
-
如果設(shè)置了
el
骇钦,自動執(zhí)行$mount()
if (vm.$options.el) { vm.$mount(vm.$options.el) }
Vue執(zhí)行流程
進(jìn)入
src\core\instance\index.js
文件內(nèi)的Vue
函數(shù)窥翩,執(zhí)行this._init(options)
寇蚊。-
再進(jìn)入
src\core\instance\init.js
文件內(nèi)的initMixin
函數(shù)糊闽。32行此時的
options
還是用戶設(shè)置的初始化選項(xiàng)右犹,data
盼忌,el
等谦纱。-
先判斷是否為
isComponent
,若不是執(zhí)行mergeOptions
將用戶設(shè)置和系統(tǒng)設(shè)置進(jìn)行合并祠乃。- 合并完成后
vm.$options
是合并后的結(jié)果,可使用全局組件等{components{KeepAlive,Transition,TransitionGroup},data,el,filters}
- 合并完成后
執(zhí)行
initProxy
進(jìn)行代理設(shè)置嘱支。-
依次執(zhí)行初始化操作,
initLifecycle(vm)
馍盟,vm新增屬性$children
,$parent
,$refs
但是值為空八毯,$root
Vue根實(shí)例。initEvents(vm)
泊交,vm新增屬性_events
事件監(jiān)聽callHook(vm, 'beforeCreate')
派發(fā)生命周期廓俭,所以此時不可以操作數(shù)據(jù)。-
initState(vm)
雹熬,數(shù)據(jù)的代理和響應(yīng)式發(fā)生在這里,vm新增數(shù)據(jù)相應(yīng)式烈菌,新增_data
(響應(yīng)式有_ob_
),$data
(非響應(yīng)式)-
進(jìn)入文件
src\core\instance\state.js
,執(zhí)行initState
函數(shù)捂襟,如果有重名按處理順序,誰先占用誰使用宠漩。if (opts.props) initProps(vm, opts.props)
處理props
扒吁。if (opts.methods) initMethods(vm, opts.methods)
處理methods
魁索。-
if (opts.data) { initData(vm) } else {observe(vm._data = {}, true) }
處理data
刽虹。- 如果有
data
執(zhí)行initData(vm)
。- 執(zhí)行
initData
函數(shù)当辐,判斷是函數(shù)還是對象再做相應(yīng)處理瀑构。 - 校驗(yàn)命名沖突世吨,根據(jù)
props
和methods
罢浇。 - 最后執(zhí)行
observe(data, true)
遞歸響應(yīng)式處理。
- 執(zhí)行
- 如果沒有
data
執(zhí)行observe(vm._data = {}, true)
胞锰。- 進(jìn)入文件
core\observer\index.js
- 獲取
Ob
實(shí)例,是否作為根數(shù)據(jù)凌那。- 如果有
_ob_
直接返回,ob = value.__ob__
- 如果沒有則創(chuàng)建励稳,
ob = new Observer(value)
- 初始化時先給
data
的值創(chuàng)建一個ob
麦锯,結(jié)果對象就有幾個ob
鹅巍。 - 創(chuàng)建
dep
實(shí)例this.dep = new Dep()
:對象也需要dep
,對象如果動態(tài)增減屬性敛苇。 -
Observer
區(qū)分對象還是數(shù)組。 - 判斷類型来涨,指定
ob
實(shí)例。- 如果時數(shù)組執(zhí)行數(shù)組響應(yīng)式
this.observeArray(value)
卧抗。
1. 進(jìn)入文件src\core\observer\array.js
。
2. 默認(rèn)的7個方法不會通知更新浦马,將原有7個方法攔截修改晶默。
3. 獲取數(shù)組原型,const arrayProto = Array.prototype
坞靶。
4. 克隆一份新原型,export const arrayMethods = Object.create(arrayProto)
尿这。
5. 7個變更方法需要覆蓋,const methodsToPatch = [ 'push', 'pop', 'shift','unshift', 'splice', 'sort', 'reverse']
叨橱。因?yàn)檫@7個會改變數(shù)組,其他的返回新數(shù)組栖博。
6. 遍歷7個方法,每次拿出一個方法保存原始方法const original = arrayProto[method]
開始覆蓋。
7. 執(zhí)行默認(rèn)方法const result = original.apply(this, args)
公你。
8. 對新加入的元素進(jìn)行響應(yīng)式if (inserted) ob.observeArray(inserted)
。
9. 變更通知const ob = this.__ob__
。
10.ob
內(nèi)部有dep
税肪,讓dep
通知更新ob.dep.notify()
锻梳。
2. 如果是對象執(zhí)行對象響應(yīng)式this.walk(value)
,執(zhí)行defineReactive(obj, keys[i])
神汹。
1. 每個key對應(yīng)一個dep
,const dep = new Dep()
。
2. 依賴收集dep.depend()
桃漾,vue2中一個組件是一個Watcher
敦迄。
1.dep:n
=>wtacher:1
罚屋,多對一撕彤。
2. 通過diff
算法比對變化。
3. 進(jìn)入文件src\core\observer\dep.js
1.depend
函數(shù)內(nèi)執(zhí)行的Dep.target.addDep(this)
猛拴,watcher.addDep()
羹铅。
2. 進(jìn)入文件src\core\observer\watcher.js
的addDep
函數(shù)。
1. 相互添加引用的過程愉昆。
2.watcher
添加dep
职员,this.newDepIds.add(id)
哥蔚,this.newDeps.push(dep)
3.dep
添加watcher
深夯,dep.addSub(this)
。
3. 用戶手動創(chuàng)建Watcher
翩肌,this.$watch(key,cb)
隶糕。
1.dep:n
=>wtacher:n
灾常,多對多。
4. 子ob也要做依賴收集工作childOb.dep.depend()
。
- 如果時數(shù)組執(zhí)行數(shù)組響應(yīng)式
- 初始化時先給
- 如果有
- 進(jìn)入文件
- 如果有
if (opts.computed) initComputed(vm, opts.computed)
處理computed
螟蒸。if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) }
處理watch
-
執(zhí)行掛載過程
vm.$mount(vm.$options.el)
。
-
進(jìn)入文件
src\platforms\web\runtime\index.js
執(zhí)行Vue.prototype.$mount
掛載函數(shù)。- 執(zhí)行
mountComponent(this, el, hydrating)
將虛擬DOM轉(zhuǎn)換成真實(shí)DOM猴誊。
- 執(zhí)行
-
進(jìn)入文件
src\core\instance\lifecycle.js
- 執(zhí)行
mountComponent
函數(shù)內(nèi)部畏吓,掛載callHook(vm, 'beforeMount')
生命周期鉤子包吝。 - 聲明
updateComponent
組件更新函數(shù)函數(shù)未執(zhí)行储矩。-
vm._render()
渲染函數(shù)。 -
vm._update()
更新函數(shù)绝编。
-
-
new Watcher
內(nèi)部會執(zhí)行updateComponent
,這次才真正執(zhí)行updateComponent
,執(zhí)行函數(shù)內(nèi)部的vm._render()
渲染函數(shù)。- 進(jìn)入文件
src\core\instance\render.js
田绑,執(zhí)行Vue.prototype._render
,渲染函數(shù)的目的是得到虛擬DOM吆你。-
vnode = render.call(vm._renderProxy, vm.$createElement)
獲得虛擬DOM咸包。
-
- 進(jìn)入文件
- 再執(zhí)行
lifecycle.js
文件內(nèi)執(zhí)行vm._update()
更新函數(shù)柠衅。- 判斷是否有虛擬DOM。
- 若沒有則通過
vm._patch_
創(chuàng)建挤忙,虛擬DOM將變成真實(shí)DOM。 - 若有則通過
vm._patch_
更新。
- 若沒有則通過
- 判斷是否有虛擬DOM。
- 執(zhí)行
new Vue({})發(fā)生了什么?
- 選項(xiàng)的合并纵苛,用戶選項(xiàng)和系統(tǒng)選項(xiàng)的合并宪躯。
- 組件實(shí)例相關(guān)的屬性初始化,
$parent
,$root
,$children
,$refs
- 監(jiān)聽自定義事件。
- 解析插槽,
$slots
,$scopeSlots
,$createElement()
- 注入祖輩傳遞下來的數(shù)據(jù)。
- 把自己內(nèi)部的狀態(tài)進(jìn)行響應(yīng)式的處理柳击,
props
,methods
,data
,computed
,watch
- 提供給后代盲再,用來隔代傳遞參數(shù)
Vue數(shù)據(jù)響應(yīng)式
- Vue中利用了JS語言特性
Object.defineProperty()西设,通過定義對象屬性getter/setter攔截對屬性的訪問。
具體實(shí)現(xiàn)是在Vue初始化時洲胖,會調(diào)用initState济榨,它會初始化data,props等绿映,這里著重關(guān)注data初始
化擒滑。 - Vue1最大問題watcher過多,項(xiàng)目大到一定程度容易崩潰叉弦,所以導(dǎo)致Vue1性能很差丐一。
- Vue2的解決方法,把watcher粒度降低淹冰,一個組件一個watcher库车。
- 如果有數(shù)據(jù)發(fā)生變化,通過Diff算法把兩次計(jì)算的結(jié)果比較樱拴,才能知道那里變化并進(jìn)行更新柠衍。
- Watcher和屬性key之間的關(guān)系是1對N,正好跟Vue1的一對N是相反晶乔。
- 當(dāng)用戶自己編寫表達(dá)式this.$watch('foo',function(){})時珍坊,會產(chǎn)生新的watcher實(shí)例,此時'foo'又跟多個watcer產(chǎn)生關(guān)系正罢,N對N阵漏。
- 在依賴收集過程中相互添加關(guān)系,
watcher.addDep()
。 - 一個對象一個Observer履怯,內(nèi)部有個大管家dep回还。
- 大管家負(fù)責(zé)對象新增或刪除屬性的更新通知。
- 一個key一個小管家dep叹洲。
-
小關(guān)節(jié)負(fù)責(zé)對應(yīng)key的值變化的更新通知柠硕。
-