在閱讀本篇文章以前建議閱讀 【源碼導讀 】在new Vue()以前,Vue是什麼樣子的?。vue
未掛載狀態的vue實例,掛載過程將在後面分析。node
小提示:配合源碼食用更佳美味。數組
在new Vue()最開始只幹了一件事,this._init()緩存
src/core/instance/index.jsbash
import { initMixin } from './init'
function Vue (options) {
this._init(options)
}
initMixin(Vue)
複製代碼
而這個this._init(),是在initMixin(Vue)中定義的app
src/core/instance/init.jsdom
篩選出第一次new Vue的代碼,閱讀註釋ide
// vue實例的惟一id
let uid = 0
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// 每次建立都會加一,確保惟一性
vm._uid = uid++
// vue實例不該該是一個響應式的,作個標記
vm._isVue = true
// 先忽略 組件相關的
if (options && options._isComponent) {
// ...
} else {
// 合併配置項
vm.$options = mergeOptions(
// 這裏是取到以前的默認配置,組件 指令 過濾器等
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
vm._self = vm
// 生命週期初始化
initLifecycle(vm)
// 事件初始化
initEvents(vm)
// render初始化
initRender(vm)
// 調用建立以前的鉤子函數
callHook(vm, 'beforeCreate')
// 注入初始化
initInjections(vm) // resolve injections before data/props
// 數據初始化
initState(vm)
// 提供初始化
initProvide(vm) // resolve provide after data/props
// 調用建立完成的鉤子函數
callHook(vm, 'created')
// 存在el則默認掛載到el上 不存在的時候不掛載 須要手動掛載
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
複製代碼
能夠看到 模塊分的很細 咱們展開說函數
src/core/instance/lifecycle.jsoop
export function initLifecycle (vm: Component) {
const options = vm.$options
// 找到第一個不是抽象組件的父親
let parent = options.parent
// 第一次是沒有父親的 不會走這個邏輯
if (parent && !options.abstract) {
// 若沒有父親 而且不是抽象組件 進到這就確定不是根組件
// 若是是抽象組件而且有父親 繼續向上找 直道找到最上面的一個抽象組件的父親
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
// 給找到的最上面的抽象組件的孩子push到本身
parent.$children.push(vm)
/* 不太好理解 畫個圖 * app -> ccc -> ddd -> transition -> eee -> fff * 好比new Vue(eee) 他就會向上找 直道找到 transition 這個抽象組件 * 圖上所描述的ddd的孩子是transition 通過這個操做後ddd的孩子就會多一個eee */
}
// 配置父組件
vm.$parent = parent
// 配置子組件
vm.$root = parent ? parent.$root : vm
// 配置孩子是空數組
vm.$children = []
// 配置 refs是空
vm.$refs = {}
// 依賴收集相關 用來更新組件
vm._watcher = null
// 我理解的是他是否已經在用狀態 沒用的話都不須要更新。請教評論區大佬
vm._inactive = null
// keepAlive可是又調用了destroy的狀況下使用
vm._directInactive = false
// 是否掛載
vm._isMounted = false
// 是否銷燬
vm._isDestroyed = false
// 是否正在被銷燬
vm._isBeingDestroyed = false
}
複製代碼
src/core/instance/events.js
export function initEvents (vm: Component) {
// 用於存放事件
vm._events = Object.create(null)
// hook事件 hook:xxxx
vm._hasHookEvent = false
// 初始化父組件附加事件 <comp @my-click="aaa">
// 參考下圖,當new Vue()是組件comp的時候
const listeners = vm.$options._parentListeners
if (listeners) {
// 更新組件的監聽 將comp的事件取出來放到本身身上監聽
updateComponentListeners(vm, listeners)
}
}
複製代碼
export function initRender (vm: Component) {
// 存放虛擬dom
vm._vnode = null
// 緩存靜態節點 只渲染元素和組件一次。v-once
vm._staticTrees = null
const options = vm.$options
// 父組件中的佔位節點
const parentVnode = vm.$vnode = options._parentVnode
// 父節點實例
const renderContext = parentVnode && parentVnode.context
// 插槽邏輯
vm.$slots = resolveSlots(options._renderChildren, renderContext)
// 插槽做用域
vm.$scopedSlots = emptyObject
// 私有變量 render函數內部使用 建立元素
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
// 規範化始終應用於公共版本,用於用戶編寫的呈現函數。 在分析render過程的時候在說 能夠簡單認爲他就是建立元素
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
const parentData = parentVnode && parentVnode.data
// 作代理 包含了父做用域中不做爲 prop 被識別 (且獲取) 的特性綁定 (class 和 style 除外)。
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
// 作代理 包含了父做用域中的 (不含 .native 修飾器的) v-on 事件監聽器。
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
}
複製代碼
在初始化data props 以前初始化 用於覆蓋。 在提供以前初始化,在提供數據以前把數據注入進來,用於提供
src/core/instance/inject.js
export function initInjections (vm: Component) {
// 若是 有注入選項就繼續向上找提供數據的父親 知道找到位置 注意是個數組
const result = resolveInject(vm.$options.inject, vm)
if (result) {
// 此數據不該該被觀察
toggleObserving(false)
Object.keys(result).forEach(key => {
// 響應數據 後續講此過程
defineReactive(vm, key, result[key])
})
// 此數據能夠被觀察
toggleObserving(true)
}
}
複製代碼
響應數據是指:數據變化會致使dom變化的數據,觀察數據就會響應數據,響應數據、依賴收集後續分析
src/core/instance/state.js
export function initState (vm: Component) {
// 依賴收集相關 後續講
vm._watchers = []
const opts = vm.$options
// 初始化props
if (opts.props) initProps(vm, opts.props)
// 初始初始化方法
if (opts.methods) initMethods(vm, opts.methods)
// 初始化數據
if (opts.data) {
initData(vm)
} else {
// 觀察數據空對象
observe(vm._data = {}, true /* asRootData */)
}
// 初始化計算屬性
if (opts.computed) initComputed(vm, opts.computed)
// 初始化wath
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
複製代碼
function initProps (vm: Component, propsOptions: Object) {
const propsData = vm.$options.propsData || {}
const props = vm._props = {}
// 緩存key用
const keys = vm.$options._propKeys = []
const isRoot = !vm.$parent
// 不是根組件須要被轉換爲響應數據
if (!isRoot) {
toggleObserving(false)
}
for (const key in propsOptions) {
keys.push(key)
// 類型檢查
const value = validateProp(key, propsOptions, propsData, vm)
// 定義響應數據
defineReactive(props, key, value)
// 做用就是 訪問this.xxx就能訪問到 this.props.xxx
if (!(key in vm)) {
proxy(vm, `_props`, key)
}
}
toggleObserving(true)
}
複製代碼
function initMethods (vm: Component, methods: Object) {
const props = vm.$options.props
for (const key in methods) {
// 改變this指向 而且 定義到this.xxx就能訪問到 this.methods.xxx
vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
}
}
複製代碼
function initData (vm: Component) {
let data = vm.$options.data
// 把函數轉換成data 此處收集依賴相關後續分析
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
if (!isPlainObject(data)) {
data = {}
}
// 檢測重複
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
const key = keys[i]
if (props && hasOwn(props, key)) {
// 開發環境會提示錯誤
} else if (!isReserved(key)) {
// 作代理
proxy(vm, `_data`, key)
}
}
// 觀察數據 後續
observe(data, true /* asRootData */)
}
複製代碼
export function initProvide (vm: Component) {
const provide = vm.$options.provide
if (provide) {
// 把數據放到vm._provided上 用戶注入數據時查找
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide
}
}
複製代碼
感謝各位的閱讀,錯誤是在所不免的,如有錯誤,或者有更好的理解,請在評論區留言,再次感謝。但願你們相互學習,共同進步。