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

问题: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.接下来进入挂载阶段

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,335评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,895评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,766评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,918评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,042评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,169评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,219评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,976评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,393评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,711评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,876评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,562评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,193评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,903评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,699评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,764评论 2 351

推荐阅读更多精彩内容