vue源码分析-从new Vue开始

2022-10-19 14:43:28 浏览数 (1)

初学vue,你得知道我们是从new Vue开始的:

代码语言:javascript复制
new Vue({
  el: '#app',
  data: obj,
  ...
})

那你觉得是不是很有意思,咱们new Vue之后,就可以使用他那么多的功能,可见Vue是暴出来的一个一个功能类函数,我们进入源码一探究竟:

代码语言:javascript复制
import Vue from './instance/index'
import { initGlobalAPI } from './global-api/index'
//判断是不是服务端渲染
import { isServerRendering } from 'core/util/env'
import { FunctionalRenderContext } from 'core/vdom/create-functional-component'
/** * 添加全局的API */
initGlobalAPI(Vue)
/** * 服务端渲染需要 */
Object.defineProperty(Vue.prototype, '$isServer', {
  get: isServerRendering
})
/** * 服务端渲染需要 */
Object.defineProperty(Vue.prototype, '$ssrContext', {
  get () {
    /* istanbul ignore next */
    return this.$vnode && this.$vnode.ssrContext
  }
})
/** * 服务端渲染需要 */
Object.defineProperty(Vue, 'FunctionalRenderContext', {
  value: FunctionalRenderContext
})
/** * vue版本号 这里的'__VERSION__'为占位符,发布版本时将被自动替换 */
Vue.version = '__VERSION__'
export default Vue

那么我们看到咱们的核心Vue来自'./instance/index'那我们再去一探究竟,可想而知里面必定有一个Vue函数类

代码语言:javascript复制
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'

// Vue构造函数必须使用new关键字实例化, 否则会抛出一个警告, 实例化Vue的时候会调用_init方法初始化
// 这里options也是.vue文件中暴露出的对象
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)
}

initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue

可以看到里面有一个function Vue功能类,而且里面加载了initMixin,stateMixin等,这几个方法分别传入了Vue来初始化一些功能。

另外我们可以在入口文件出看到initGlobalAPI这个方法,那么我们打开initGlobalAPI所在的位置:./global-api/index

代码语言:javascript复制
......
export function initGlobalAPI (Vue: GlobalAPI) {
  // config
  // 提供获取配置项的方法, 不允许在这里设置配置项
  const configDef = {}
  configDef.get = () => config
  if (process.env.NODE_ENV !== 'production') {
    configDef.set = () => {
      warn(
        'Do not replace the Vue.config object, set individual fields instead.'
      )
    }
  }
  Object.defineProperty(Vue, 'config', configDef)

  // exposed util methods.
  // NOTE: these are not considered part of the public API - avoid relying on
  // them unless you are aware of the risk.
  // 注册全局工具API, 只对Vue生效。
  Vue.util = {
    warn,
    extend,
    mergeOptions,
    defineReactive
  }
  //定义全局属性
  Vue.set = set
  Vue.delete = del
  Vue.nextTick = nextTick

  // 初始化options
  Vue.options = Object.create(null)
  ASSET_TYPES.forEach(type => {
    Vue.options[type   's'] = Object.create(null)
  })

  // this is used to identify the "base" constructor to extend all plain-object
  // components with in Weex's multi-instance scenarios.
  // 用_base属性来挂载Vue构造器
  Vue.options._base = Vue

  extend(Vue.options.components, builtInComponents)
  //定义全局方法
  initUse(Vue) // Vue.use
  initMixin(Vue) // Vue.mixin
  initExtend(Vue) // Vue.extend
  initAssetRegisters(Vue)
}

可见暴露出多个方法给全局,而Vue.util是一些工具方法:

代码语言:javascript复制
import config from '../config'
import { initUse } from './use'
import { initMixin } from './mixin'
import { initExtend } from './extend'
import { initAssetRegisters } from './assets'
import { set, del } from '../observer/index'
import { ASSET_TYPES } from 'shared/constants'
import builtInComponents from '../components/index'

import {
  warn,
  extend,
  nextTick,
  mergeOptions,
  defineReactive
} from '../util/index'

那么我们回到Vue功能函数类上:

代码语言:javascript复制
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'

// Vue构造函数必须使用new关键字实例化, 否则会抛出一个警告, 实例化Vue的时候会调用_init方法初始化
// 这里options也是.vue文件中暴露出的对象
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)
}

initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue

我们可以看到initMixin(Vue)执行了,那么我们去读一下init的源码:

参考Vue3源码视频讲解:进入学习

代码语言:javascript复制
import config from '../config'
import { initProxy } from './proxy'
import { initState } from './state'
import { initRender } from './render'
import { initEvents } from './events'
import { mark, measure } from '../util/perf'
import { initLifecycle, callHook } from './lifecycle'
import { initProvide, initInjections } from './inject'
import { extend, mergeOptions, formatComponentName } from '../util/index'

let uid = 0

// 向Vue.prototype添加_init方法, 用于new Vue时 初始化一些配置项
// 主要的功能是合并配置项, 初始化生命周期, 事件, 组件, 指令等等
export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // a uid
    // 使用uid来区分不同的vue实例, 以便后面的缓存功能使用
    vm._uid = uid  

    let startTag, endTag
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag)
    }

    // 如果是vue实例,则不需要被observe
    vm._isVue = true
    // 1、处理options参数的处理
    if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm, options)
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    //2、renderProxy
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    // 依次初始化配置项, 并调用生命周期钩子
    vm._self = vm
    initLifecycle(vm)
    // 事件监听初始化
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    //初始化vm状态 prop/data/computed/watch完成初始化
    initState(vm)
    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)
    }

    // 配置项里有el属性, 则会挂载到真实DOM上, 完成视图的渲染
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

阅读init.js源码之后,我们可以看到其实就是这样一个顺序:

  • option参数
  • renderProxy代理
  • vm的生命周期相关变量初始化
  • vm的事件监听
  • 初始化vm的状态
  • render & $mount

在Vue的原型上挂了一个_init方法,也就是说,我们执行new Vue(options)的时候就会执行这个方法,并且我们传过去的options可以通过vm.$options访问到,那么mergeOptions()内部原理又是啥呢,而且我们看到他接收到两个部分resolveConstructorOptions(),options并且融合在一起,那么我们先研究resolveConstructorOptions()

代码语言:javascript复制
export function resolveConstructorOptions (Ctor: Class<Component>) {
  let options = Ctor.options
  if (Ctor.super) {
    const superOptions = resolveConstructorOptions(Ctor.super)
    const cachedSuperOptions = Ctor.superOptions
    if (superOptions !== cachedSuperOptions) {
      // super option changed,
      // need to resolve new options.
      Ctor.superOptions = superOptions
      // check if there are any late-modified/attached options (#4976)
      const modifiedOptions = resolveModifiedOptions(Ctor)
      // update base extend options
      if (modifiedOptions) {
        extend(Ctor.extendOptions, modifiedOptions)
      }
      options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
      if (options.name) {
        options.components[options.name] = Ctor
      }
    }
  }
  return options
}

那么问题又来了,let options = Ctor.options中的Ctor又是什么东西呢?我们可以看到下面extend(Ctor.extendOptions, modifiedOptions)那我们去找extend:

代码语言:javascript复制
import { ASSET_TYPES } from 'shared/constants'
import { defineComputed, proxy } from '../instance/state'
import { extend, mergeOptions, validateComponentName } from '../util/index'

export function initExtend (Vue: GlobalAPI) {
  /**   * Each instance constructor, including Vue, has a unique   * cid. This enables us to create wrapped "child   * constructors" for prototypal inheritance and cache them.   */
  // 为每个构造函数分配一个唯一的cid, 以便用于缓存
  Vue.cid = 0
  let cid = 1

  /**   * Class inheritance   */
  // 生成Vue构造器的子类
  Vue.extend = function (extendOptions: Object): Function {
    extendOptions = extendOptions || {}
    // 如果cid已存在, 则应用缓存, 直接返回缓存的构造器
    const Super = this
    const SuperId = Super.cid
    const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
    if (cachedCtors[SuperId]) {
      return cachedCtors[SuperId]
    }

    const name = extendOptions.name || Super.options.name
    if (process.env.NODE_ENV !== 'production' && name) {
      validateComponentName(name)
    }
    // 创建一个包含_init方法的子类, 并赋予唯一的cid
    const Sub = function VueComponent (options) {
      this._init(options)
    }
    Sub.prototype = Object.create(Super.prototype)
    Sub.prototype.constructor = Sub
    Sub.cid = cid  
    // 合并配置项
    Sub.options = mergeOptions(
      Super.options,
      extendOptions
    )
    Sub['super'] = Super

    // For props and computed properties, we define the proxy getters on
    // the Vue instances at extension time, on the extended prototype. This
    // avoids Object.defineProperty calls for each instance created.
    // 将props和computed都代理到vm实例上
    if (Sub.options.props) {
      initProps(Sub)
    }
    if (Sub.options.computed) {
      initComputed(Sub)
    }

    // allow further extension/mixin/plugin usage
    // 添加extend,mixin,use这些API
    Sub.extend = Super.extend
    Sub.mixin = Super.mixin
    Sub.use = Super.use

    // create asset registers, so extended classes
    // can have their private assets too.
    ASSET_TYPES.forEach(function (type) {
      Sub[type] = Super[type]
    })
    // 将组件实例自身也挂载为一个属性, 用于递归组件
    // enable recursive self-lookup
    if (name) {
      Sub.options.components[name] = Sub
    }

    // keep a reference to the super options at extension time.
    // later at instantiation we can check if Super's options have
    // been updated.
    Sub.superOptions = Super.options
    Sub.extendOptions = extendOptions
    Sub.sealedOptions = extend({}, Sub.options)

    // cache constructor
    cachedCtors[SuperId] = Sub
    return Sub
  }
}

那我么找到了Vue.extend,可以看出来这不就是实现了一个继承嘛。Sub继承自super,然后return出去。

所以resolveConstructorOptions就做两件事

  • Ctor.super 来判断该类是否是Vue的子类
  • if (superOptions !== cachedSuperOptions) 来判断父类中的options 有没有发生变化 那么我们知道了resolveConstructorOptionsnew Vue(options),我们的mergeOptions函数就是把他们合并的:
代码语言:javascript复制
// 合并组件的配置项
export function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  if (process.env.NODE_ENV !== 'production') {
    checkComponents(child)
  }

  if (typeof child === 'function') {
    child = child.options
  }

  // 序列化props, inject, directive
  normalizeProps(child, vm)
  normalizeInject(child, vm)
  normalizeDirectives(child)
  const extendsFrom = child.extends
  if (extendsFrom) {
    parent = mergeOptions(parent, extendsFrom, vm)
  }
  // child的mixins加入parent中
  if (child.mixins) {
    for (let i = 0, l = child.mixins.length; i < l; i  ) {
      parent = mergeOptions(parent, child.mixins[i], vm)
    }
  }
  // 使用strat中的合并方法去依次合并配置对象
  const options = {}
  let key
  for (key in parent) {
    mergeField(key)
  }
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key)
    }
  }
  function mergeField (key) {
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}

那么我们写的一些组件和特性全部放在vm.$options里面了,那么下一步也就是renderProxy咱们有兴趣的童鞋,可以依据引用路径去看看实现原理。

那么我们initMixin函数执行完前两步之后需要执行的几个函数分别为

代码语言:javascript复制
    // 依次初始化配置项, 并调用生命周期钩子
    vm._self = vm
    initLifecycle(vm)
    // 事件监听初始化
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    //初始化vm状态 prop/data/computed/watch完成初始化
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')

那我们先来了解一下这几个函数分别干了啥:

1、initLifecycle

代码语言:javascript复制
export function initLifecycle (vm: Component) {
  const options = vm.$options

  // locate first non-abstract parent
  let parent = options.parent
  if (parent && !options.abstract) {
    while (parent.$options.abstract && parent.$parent) {
      parent = parent.$parent
    }
    parent.$children.push(vm)
  }
  vm.$parent = parent
  vm.$root = parent ? parent.$root : vm
  vm.$children = []
  vm.$refs = {}
  vm._watcher = null
  vm._inactive = null
  vm._directInactive = false
  vm._isMounted = false
  vm._isDestroyed = false
  vm._isBeingDestroyed = false
}

2、initEvents

代码语言:javascript复制
export function initEvents (vm: Component) {
  vm._events = Object.create(null)
  vm._hasHookEvent = false
  // init parent attached events
  const listeners = vm.$options._parentListeners
  if (listeners) {
    updateComponentListeners(vm, listeners)
  }
}

3、initRender

代码语言:javascript复制
export function initRender (vm: Component) {
  vm._vnode = null // the root of the child tree
  vm._staticTrees = null // v-once cached trees
  const options = vm.$options
  const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree
  const renderContext = parentVnode && parentVnode.context
  vm.$slots = resolveSlots(options._renderChildren, renderContext)
  vm.$scopedSlots = emptyObject
  // bind the createElement fn to this instance
  // so that we get proper render context inside it.
  // args order: tag, data, children, normalizationType, alwaysNormalize
  // internal version is used by render functions compiled from templates
  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
  // normalization is always applied for the public version, used in
  // user-written render functions.
  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)

  // $attrs & $listeners are exposed for easier HOC creation.
  // they need to be reactive so that HOCs using them are always updated
  const parentData = parentVnode && parentVnode.data

  /* istanbul ignore else */
  if (process.env.NODE_ENV !== 'production') {
    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => {
      !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
    }, true)
    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => {
      !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
    }, true)
  } else {
    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
  }
}

4、callback

代码语言:javascript复制
export function callHook (vm: Component, hook: string) {
  const handlers = vm.$options[hook]
  if (handlers) {
    for (let i = 0, j = handlers.length; i < j; i  ) {
      try {
        handlers[i].call(vm)
      } catch (e) {
        handleError(e, vm, `${hook} hook`)
      }
    }
  }
  if (vm._hasHookEvent) {
    vm.$emit('hook:'   hook)
  }
}

那我们在initRender中可以发现初始化了一些功能,例如$lintener,$createElement,而在callback里面却直接调用了beforeCreate钩子函数。

后面还有

5、initInjections,resolveInject,initProvide

代码语言:javascript复制
export function initInjections (vm: Component) {
  const result = resolveInject(vm.$options.inject, vm)
  if (result) {
    toggleObserving(false)
    Object.keys(result).forEach(key => {
      /* istanbul ignore else */
      if (process.env.NODE_ENV !== 'production') {
        defineReactive(vm, key, result[key], () => {
          warn(
            `Avoid mutating an injected value directly since the changes will be `  
            `overwritten whenever the provided component re-renders. `  
            `injection being mutated: "${key}"`,
            vm
          )
        })
      } else {
        defineReactive(vm, key, result[key])
      }
    })
    toggleObserving(true)
  }
}
代码语言:javascript复制
export function resolveInject (inject: any, vm: Component): ?Object {
  if (inject) {
    // inject is :any because flow is not smart enough to figure out cached
    const result = Object.create(null)
    const keys = hasSymbol
      ? Reflect.ownKeys(inject).filter(key => {
        /* istanbul ignore next */
        return Object.getOwnPropertyDescriptor(inject, key).enumerable
      })
      : Object.keys(inject)

    for (let i = 0; i < keys.length; i  ) {
      const key = keys[i]
      const provideKey = inject[key].from
      let source = vm
      while (source) {
        if (source._provided && hasOwn(source._provided, provideKey)) {
          result[key] = source._provided[provideKey]
          break
        }
        source = source.$parent
      }
      if (!source) {
        if ('default' in inject[key]) {
          const provideDefault = inject[key].default
          result[key] = typeof provideDefault === 'function'
            ? provideDefault.call(vm)
            : provideDefault
        } else if (process.env.NODE_ENV !== 'production') {
          warn(`Injection "${key}" not found`, vm)
        }
      }
    }
    return result
  }
}
代码语言:javascript复制
export function initProvide (vm: Component) {
  const provide = vm.$options.provide
  if (provide) {
    vm._provided = typeof provide === 'function'
      ? provide.call(vm)
      : provide
  }
}

那么关于这三个函数是干嘛的?我们先来了解一下:

provide/inject:

代码语言:txt复制
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。

那就是相当于初始化依赖的关系,而initProvide基本没有什么内容,就是将$options里的provide赋值到当前实例上

写到这里那么激动人心的时刻到了--initState

代码语言:javascript复制
export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  // 如果选项中不存在data,调用observe来观测一个空对象
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  if (opts.computed) initComputed(vm, opts.computed)
  // 这里需要额外判断一下opts.watch !== nativeWatch
  // 是因为在Firefox下, Object实例上会有一个自带的watch属性, 需要判断这个watch必须是vue提供的watch
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

可以看出initState里面初始化了propsdatacomputedwatchmethods 那么我们主要看一下initData,initProps的代码:

代码语言:javascript复制
/** * data初始化, 和props基本一致 */
function initData (vm: Component) {
  let data = vm.$options.data
  /**   * data字段有两种可选类型   * 当data是function时, 直接执行这个function 并将返回值作为data   * data也可以直接就是一个object, 但仅应该在root组件这样做, 因为直接使用data对象会导致多个相同的组件持有同一个data对象的引用   * 而使用一个返回新对象的function就可以避免这个问题   * 详见 https://cn.vuejs.org/v2/style-guide/#组件数据-必要   * 其实在initData之前的mergeOptions操作中已经将data格式化为一个function   * 但是在initData和mergeOptions中间还有一个生命周期钩子beforeCreate被调用   * 这里使用typeof再次判断data的类型是为了防止在beforeCreate中改变了vm.$options.data   */
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  // 防止开发者在写data选项时不按规矩返回一个对象
  if (!isPlainObject(data)) {
    data = {}
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object:n'  
      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
      vm
    )
  }
  // proxy data on instance
  // 将data的每个字段都代理到vm实例上, 并判断是否与methods, props冲突, 重复key会报错
  const keys = Object.keys(data)
  const props = vm.$options.props
  const methods = vm.$options.methods
  let i = keys.length
  // 检查是否有重复的key, 这里可以看出props优先级最高, 其次是data, 最后是methods
  // 真的出现重名时,会按照优先级覆盖
  while (i--) {
    const key = keys[i]
    if (process.env.NODE_ENV !== 'production') {
      if (methods && hasOwn(methods, key)) {
        warn(
          `Method "${key}" has already been defined as a data property.`,
          vm
        )
      }
    }
    if (props && hasOwn(props, key)) {
      process.env.NODE_ENV !== 'production' && warn(
        `The data property "${key}" is already declared as a prop. `  
        `Use prop default value instead.`,
        vm
      )
    // isReserved用于判断一个key是否以$或者_开头,vue不会在vm上面代理这些key
    } else if (!isReserved(key)) {
      proxy(vm, `_data`, key)
    }
  }
  // observe data
  // 使data变为响应式
  observe(data, true /* asRootData */)
}

function initProps (vm: Component, propsOptions: Object) {
  const propsData = vm.$options.propsData || {}
  const props = vm._props = {}
  // cache prop keys so that future props updates can iterate using Array
  // instead of dynamic object key enumeration.
  const keys = vm.$options._propKeys = []
  const isRoot = !vm.$parent
  // root instance props should be converted
  // 使用toggleObserving可以设置shouldObserve为参数的值, 这里设为false是阻止观察者响应更新, 直到props初始化结束
  if (!isRoot) {
    toggleObserving(false)
  }
  // 遍历所有props选项, 并校验props
  for (const key in propsOptions) {
    keys.push(key)
    const value = validateProp(key, propsOptions, propsData, vm)
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      const hyphenatedKey = hyphenate(key)
      // 如果使用了保留属性, 这里会报错, 保留属性包括key,ref,slot,slot-scope,is
      if (isReservedAttribute(hyphenatedKey) ||
          config.isReservedAttr(hyphenatedKey)) {
        warn(
          `"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
          vm
        )
      }
      // isUpdatingChildComponent会在组件开始更新时置为true, 完成更新时置为false
      // 如果子组件中更改一个props时会导致父组件也重新渲染, 这里就会抛出警告
      defineReactive(props, key, value, () => {
        if (vm.$parent && !isUpdatingChildComponent) {
          warn(
            `Avoid mutating a prop directly since the value will be `  
            `overwritten whenever the parent component re-renders. `  
            `Instead, use a data or computed property based on the prop's `  
            `value. Prop being mutated: "${key}"`,
            vm
          )
        }
      })
    } else {
      // 使props变为响应式属性
      defineReactive(props, key, value)
    }
    // static props are already proxied on the component's prototype
    // during Vue.extend(). We only need to proxy props defined at
    // instantiation here.
    // 将props都代理到vm实例上, 以便代码中可以通过this.propName去访问到对应的props
    if (!(key in vm)) {
      proxy(vm, `_props`, key)
    }
  }
  // 初始化完毕, 开启响应式更新
  toggleObserving(true)
}

上述代码实现了把propsdata变为响应式,后面的computedwatch暂时不讲,后续专门有一篇文章来写他们。

那么initState之后我们还会执行一个callback函数,传入的是created参数,调用钩子函数created这个时候也就是页面已经创建了,并且下一个生命周期为beforMount,在讲挂载之前,一定要讲响应式系统原理

0 人点赞