new Vue的初始化流程
new Vue({})之後發生了什麼?
首先來看Vue的構造函數,
一、src/core/instance/index.js
源碼:
function Vue(options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
可以看到這裏會進入到_init()這個方法中。即Vue.prototype._init,所在文件爲src/core/instance/init.js
二、src/core/instance/init.js
可以在這個文件中看到,_init()方法定義了很多的初始化方法, _init()源碼:
export function initMixin(Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++
....中間代碼忽略,從45行看起
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
initLifecycle(vm) //聲明$parent,$root,$parent,$refs
initEvents(vm) //對父組件傳入的事件添加監聽,事件是誰創建誰監聽,子組件創建事件子組件監聽
initRender(vm) //聲明$slots和$createElement()
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props 獲取注入數據
initState(vm) //(重要)數據響應式:初始化狀態:props,methods,data,computed,watch
initProvide(vm) // resolve provide after data/props //提供數據注入
callHook(vm, 'created')
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
vm._name = formatComponentName(vm, false)
mark(endTag)
measure(`vue ${vm._name} init`, startTag, endTag)
}
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
2.1 我們來描述一下這些初始化方法:
- initProxy:作用域代理,攔截組件內訪問其它組件的數據。
- initLifecycle:建立父子組件關係,在當前組件實例上添加一些屬性和生命週期標識。如refs,$children,_isMounted等。
- initEvents:對父組件傳入的事件添加監聽,事件是誰創建誰監聽,子組件創建事件子組件監聽
- initRender:聲明createElement()等。
- initInjections:注入數據,初始化inject,一般用於組件更深層次之間的通信。
- initState:重要)數據響應式:初始化狀態。很多選項初始化的彙總:data,methods,props,computed和watch。
- initProvide:提供數據注入。**思考:爲什麼先注入再提供呢??**答:1、首先來自祖輩的數據要和當前實例的data,等判重,相結合,所以注入數據的initInjections一定要在InitState的上面。2、從上面注入進來的東西在當前組件中轉了一下又提供給後代了,所以注入數據也一定要在上面。
- vm.options.el):掛載實例。
三、接下來再從_init()方法的initLifecycle()開始初始化生命週期。
mountComponent: src/core/instance/lifecircle.js
執行掛載:在這個文件中獲取VDom並轉換爲真實DOM。
首先在lifecircle.js文件中,定義了Vue.prototype._update 方法,這個方法會獲取Vdom,接着執行mountComponent,然後執行vm._update(vnode, hydrating),源碼如下:
let updateComponent
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
updateComponent = () => {
const name = vm._name
const id = vm._uid
const startTag = `vue-perf-start:${id}`
const endTag = `vue-perf-end:${id}`
mark(startTag)
const vnode = vm._render()
mark(endTag)
measure(`vue ${name} render`, startTag, endTag)
mark(startTag)
vm._update(vnode, hydrating) //把虛擬Dom轉化爲真實dom
mark(endTag)
measure(`vue ${name} patch`, startTag, endTag)
}
} else {
updateComponent = () => {
vm._update(vm._render(), hydrating) //執行render() 獲取vdom並渲染dom
}
}
四、接着在updateComponent這個方法中執行vm._update()方法會進入src/core/instance/render.js文件 中
Vue.prototype._render = function (): VNode {
const vm: Component = this
const { render, _parentVnode } = vm.$options
if (_parentVnode) {
vm.$scopedSlots = normalizeScopedSlots(
_parentVnode.data.scopedSlots,
vm.$slots,
vm.$scopedSlots
)
}
// set parent vnode. this allows render functions to have access
// to the data on the placeholder node.
vm.$vnode = _parentVnode
// render self
let vnode
try {
// There's no need to maintain a stack because all render fns are ui4op[poiuy]
// separately from one another. Nested component's render fns are called
// when parent component is patched.
currentRenderingInstance = vm
//這裏把vdom轉化爲了真實dom
vnode = render.call(vm._renderProxy, vm.$createElement)
}
通過這句vnode = render.call(vm._renderProxy, vm.$createElement)把vdom替換成了真實dom。
始始化流程總結
new Vue() (vue構造函數) => _init() (初始化options中的各種屬性,data,methods,props,computed,watch)=> $mount (調用mountComponent())===> mountComponent() (創建updateComponent()) ===> updateComponent() (執行_render()和_update()) ===> _render() (獲取虛擬dom) ===> update() (把獲取的虛擬dom轉化爲真實dom)
這裏有一個例子來說明:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src='../../dist/vue.js'></script>
</head>
<body>
<div id="app">
<h2>數據初始化</h2>
<p>{{foo}}</p>
</div>
<script>
new Vue({
el: '#app',
data() {
return {
foo: 'fooooooo'
}
},
})
</script>
</body>
</html>
在瀏覽器中打開這個文件,斷點設置在new Vue({})這裏,F11進入到這個構造函數內部。
調試過程如下:
- 步驟8中的執行完成,界面上的{{foo}}就變成了foo,至此初始化過程就完成了。