美文网首页
vue源码(1):Vue 初始化过程

vue源码(1):Vue 初始化过程

作者: 执念_01a7 | 来源:发表于2022-09-09 16:02 被阅读0次

问题:new Vue(options) 发生了什么?

1.处理组件配置项

  • initInternalComponent(性能优化,减少原型链的动态查找),初始化每个子组件时做了一些性能优化,将组件配置对象上的一些深层次属性放到 vm.$options 选项中,以提高代码的执行效率。
  • mergeOptions (选项合并),根组件初始化时进行了选项合并,将全局配置的选项合并到跟组建的局部配置上。
  • 源码文件路径 :/src/core/instance/init.js
export function initMixin(Vue: Class<Component>) {
  // 初始化Vue的过程
  Vue.prototype._init = function (options?: Object) {
    //vue实例
    const vm: Component = this;
    // 每个vue实例都有一个递增uid
    vm._uid = uid++;
    vm._isVue = true;
    //配置组件配置项
    if (options && options._isComponent) {
      /*性能优化,减少原型链的动态查找,提高执行效率
       *每个子组件初始化时走这里,这里做了一些性能优化
       *将组件一些深层次的属性放入vm.$option中,提高代码效率
       */
      initInternalComponent(vm, options);
    } else {
      /**
       *初始化根组件时走这里,合并 Vue 的全局配置到根组件的局部配置,比如 Vue.component 注册的全局组件会合并到 根实例的 components 选项中
       *至于每个子组件的选项合并则发生在两个地方:
       *  1、Vue.component 方法注册的全局组件在注册时做了选项合并
       *  2、{ components: { xx } } 方式注册的局部组件在执行编译器生成的 render 函数时做了选项合并,包括根组件中的 components 配置
       * */
      // 根组件选项合并,将全局配置合并到根组件的局部配置上
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      );
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== "production") {
      // 设置代理,将 vm 实例上的属性代理到 vm.renderProxy
      initProxy(vm);
    } else {
      vm._renderProxy = vm;
    }
    // expose real self
    vm._self = vm;
    //初始化组件实例关系属性,比如 $parent、$children、$root、$refs 等
    initLifecycle(vm);
    /**
     * 初始化自定义事件,这里需要注意一点,<comp @click="handelClick"> 上注册的事件,监听者不是父组件,
     * 而是子组件本身,也就是说事件的派发和监听这都是子组件本身,和父组件无关
     *
     **/
    initEvents(vm);
    //解析组件的插槽信息,得到vm.$slot处理渲染函数,得到vm.$ ceraterElement方法
    initRender(vm);
    //
    callHook(vm, "beforeCreate");
    initInjections(vm); // resolve injections before data/props
    initState(vm);
    initProvide(vm); // resolve provide after data/props
    callHook(vm, "created");

    if (vm.$options.el) {
      vm.$mount(vm.$options.el);
    }
  };
}

2.初始化实例关系,比如 parent、children、root、refs 等

  • initLifecycle()
  • 源码文件路径:src\core\instance\lifecycle.js
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
}

3.处理自定义事件

  • initEvents
  • 源码文件路径 :src\core\instance\events.js
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)
  }
}
  • 问题思考:父组件调用子组件时子组件调用的事件是谁在监听
 <comp @click="handleClick" /> 
//通过vue的初始化 this.$emit('click')会被转为this.$on('click',function handleClick(){})
//这里的this指的是子组件,所以子组件事件调用以后是子组件自己在监听自己

4.调用 beforeCreate 钩子函数

  • callHook(vm, "beforeCreate")
  • 源码文件路径:src\core\instance\lifecycle.js
export function callHook (vm: Component, hook: string) {
  // #7573 disable dep collection when invoking lifecycle hooks
  pushTarget()
  const handlers = vm.$options[hook]
  const info = `${hook} hook`
  if (handlers) {
    for (let i = 0, j = handlers.length; i < j; i++) {
      invokeWithErrorHandling(handlers[i], vm, null, vm, info)
    }
  }
  if (vm._hasHookEvent) {
    vm.$emit('hook:' + hook)
  }
  popTarget()
}

5.初始化组件的 inject 配置项(inject 为一个高阶函数可在vue.js文档中查看具体的使用方法)

  • initInjections
  • 源码文件路径 :/src/core/instance/inject.js
/**
 * 解析 inject 配置项,从祖代组件的 provide 配置中找到 key 对应的值,否则用 默认值,最后得到 result[key] = val
 * inject 对象肯定是以下这个结构,因为在 合并 选项时对组件配置对象做了标准化处理
 * @param {*} inject = {
 *  key: {
 *    from: provideKey,
 *    default: xx
 *  }
 * }
 */
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)
    // inject 配置项的所有的 key
    const keys = hasSymbol
      ? Reflect.ownKeys(inject)
      : Object.keys(inject)

    // 遍历 key
    for (let i = 0; i < keys.length; i++) {
      const key = keys[i]
      // 跳过 __ob__ 对象
      // #6574 in case the inject object is observed...
      if (key === '__ob__') continue
      // 拿到 provide 中对应的 key
      const provideKey = inject[key].from
      let source = vm
      // 遍历所有的祖代组件,直到 根组件,找到 provide 中对应 key 的值,最后得到 result[key] = provide[provideKey]
      while (source) {
        if (source._provided && hasOwn(source._provided, provideKey)) {
          result[key] = source._provided[provideKey]
          break
        }
        source = source.$parent
      }
      // 如果上一个循环未找到,则采用 inject[key].default,如果没有设置 default 值,则抛出错误
      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
  }
}

6. 数据响应式的处理 props、methods、data、computed、watch

  • initState
  • 源码文件路径 :src\core\instance\state.js
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)
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

7.解析组件配置项上的 provide 对象,将其挂载到 vm._provided 属性上

  • initProvide()
  • 源码文件路径 :\src\core\instance\inject.js
  • 注:inject()和 provide()是组合使用的高阶方法 provide负责提供inject则负责子组件注入
export function initProvide (vm: Component) {
  const provide = vm.$options.provide
  if (provide) {
    vm._provided = typeof provide === 'function'
      ? provide.call(vm)
      : provide
  }
}

8.调用 created 钩子函数

  • callHook(vm, 'created')
  • 源码文件路径:src\core\instance\lifecycle.js

9.如果发现配置项上有 el 选项,则自动调用 mount 方法,也就是说有了 el 选项,就不需要再手动调用mount 方法,反之,没提供 el 选项则必须调用 $mount

  • 源码文件路径:src\core\instance\init.js
 if (vm.$options.el) {
      vm.$mount(vm.$options.el);
    }

10.接下来进入挂载阶段

相关文章

网友评论

      本文标题:vue源码(1):Vue 初始化过程

      本文链接:https://www.haomeiwen.com/subject/ttvigrtx.html