【源码解析】创建Vue实例时干了什么?

2,080

前言

在阅读本篇文章之前建议阅读 【源码导读 】在new Vue()之前,Vue是什么样子的?

未挂载状态的vue实例,挂载过程将在后面分析。

小提示:配合源码食用更佳美味。

vue构造函数

在new Vue()最开始只干了一件事,this._init()

src/core/instance/index.js

import { initMixin } from './init'
function Vue (options) {
  this._init(options)
}
initMixin(Vue)

而这个this._init(),是在initMixin(Vue)中定义的

init

src/core/instance/init.js

筛选出第一次new Vue的代码,阅读注释


// 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.js

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

系列

结尾

感谢各位的阅读,错误是在所难免的,若有错误,或者有更好的理解,请在评论区留言,再次感谢。希望大家相互学习,共同进步。