【源碼解析】建立Vue實例時幹了什麼?

前言

在閱讀本篇文章以前建議閱讀 【源碼導讀 】在new Vue()以前,Vue是什麼樣子的?vue

未掛載狀態的vue實例,掛載過程將在後面分析。node

小提示:配合源碼食用更佳美味。數組

vue構造函數

在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

init

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)
  }
}
複製代碼

render初始化

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)
  }
}
複製代碼

initProps

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
  }
}

複製代碼

系列

結尾

感謝各位的閱讀,錯誤是在所不免的,如有錯誤,或者有更好的理解,請在評論區留言,再次感謝。但願你們相互學習,共同進步。

相關文章
相關標籤/搜索