vue源码分析-初始化操作

vue

function Vue (options) {
  // vue必须通过new关键字创建,否则报错
  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)
}

1. vue init方法

  1. 在原型上添加init方法,用于初始化
  2. 添加_uid,给vue实例加上唯一ID
  3. 判断是否是组件,如果是组件,需要初始化组件选项
      export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
        // 深拷贝options
        const opts = vm.$options = Object.create(vm.constructor.options)
        // doing this because it's faster than dynamic enumeration.
        // 赋值父节点
        const parentVnode = options._parentVnode
        opts.parent = options.parent
        opts._parentVnode = parentVnode
    
        const vnodeComponentOptions = parentVnode.componentOptions
        // 将父节点上的属性参数赋值
        opts.propsData = vnodeComponentOptions.propsData
        opts._parentListeners = vnodeComponentOptions.listeners
        opts._renderChildren = vnodeComponentOptions.children
        opts._componentTag = vnodeComponentOptions.tag
    
        // 查看有没有渲染函数,有则赋值
        if (options.render) {
          opts.render = options.render
          opts.staticRenderFns = options.staticRenderFns
        }
      }
    
  4. 如果不是组件,则直接执行数据合并 mergeOptions
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    
      export function resolveConstructorOptions (Ctor: Class<Component>) {
        // 获取当前构造函数上的options
        let options = Ctor.options
        // 查看当前构造函数是否是继承来的
        if (Ctor.super) {
          // 如果是继承来的,就合并父类的options到子类,递归查找,直到最顶层
          const superOptions = resolveConstructorOptions(Ctor.super)
          // 缓存本次的options
          const cachedSuperOptions = Ctor.superOptions
          // 如果两次的options不一样
          if (superOptions !== cachedSuperOptions) {
            // super option changed,
            // need to resolve new options.
            // 重置superOptions的值为本次循环options的值
            Ctor.superOptions = superOptions
            // check if there are any late-modified/attached options (#4976)
            // 检查两次options之间的修改值
            const modifiedOptions = resolveModifiedOptions(Ctor)
            // update base extend options
            // 将modifiedOptions上的值合并到extendOptions上面去
            if (modifiedOptions) {
              extend(Ctor.extendOptions, modifiedOptions)
            }
            // 执行mergeOptions方法,合并options,执行相关合并策略
            options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
            if (options.name) {
              options.components[options.name] = Ctor
            }
          }
        }
        return options
      }
    
主要是mergeOptions方法
  1. 检查component的名称是否是违禁的,不能使用HTML名称和关键字

  2. 由于vue的组件必须是function,所以需要判断下子类的类型,获取子类的options

    // 判断是否是组件
    if (typeof child === 'function') {
      child = child.options
    }
    
  3. 检查对应的选项的规范,排除不规范的选项

      normalizeProps(child, vm)
      normalizeInject(child, vm)
      normalizeDirectives(child)
    
  4. 将extends继承来的属性和mixin混合的属性都添加到options中去

    1. 遍历父级的options
    2. 找到对应的默认执行策略,如果没有,就用默认执行策略
    3. 遍历子类的options,添加子类的私有属性,默认的策略是如果子类有就用子类的,否则就用父类的
      // 遍历parent的值,看看child中是否有,如果有就替换
      for (key in parent) {
          mergeField(key)
      }
    
      // 遍历child,添加child私有属性值
      for (key in child) {
          if (!hasOwn(parent, key)) {
              mergeField(key)
          }
      }
      
      // 合并策略
      function mergeField (key) {
          // strats 是自定义策略的集合
          //  defaultStrat 是默认策略 
          const strat = strats[key] || defaultStrat
          options[key] = strat(parent[key], child[key], vm, key)
      }
    
      /**
      * Default strategy.
      * 判断子类是否存在,不存在就用父类的,否则就用子类的
      */
        const defaultStrat = function (parentVal: any, childVal: any): any {
            return childVal === undefined
                ? parentVal
                : childVal
        }
    
    1. data合并策略
      组件data必须为function,原因是为了提高复用性

        if (!vm) {
            // 判断子类如果data不是function,就报出警告
            if (childVal && typeof childVal !== 'function') {
                process.env.NODE_ENV !== 'production' && warn(
                'The "data" option should be a function ' +
                'that returns a per-instance value in component ' +
                'definitions.',
                vm
          )
      
            return parentVal
        }
        return mergeDataOrFn(parentVal, childVal)
      }
      

      具体合并逻辑

          /**
            * Helper that recursively merges two data objects together.
          */
        function mergeData (to: Object, from: ?Object): Object {
              if (!from) return to
              let key, toVal, fromVal
      
              const keys = hasSymbol
                        ? Reflect.ownKeys(from)
                      : Object.keys(from)
              
              // 遍历父类的data的数据
              for (let i = 0; i < keys.length; i++) {
                  key = keys[i]
                  // in case the object is already observed...
                  // 如果已经添加到响应式系统,就直接跳过
                  if (key === '__ob__') continue
                  // 子类的值
                  toVal = to[key]
                  // 父类的值
                  fromVal = from[key]
                  // 如果子类中不存在这个属性,就在子类中添加这个属性并赋值
                  if (!hasOwn(to, key)) {
                      set(to, key, fromVal)
                  } else if (
                      // 判断如果子类和父类都有对应的属性值,并且都是对象,就递归调用当前方法合并
                      toVal !== fromVal &&
                      isPlainObject(toVal) &&
                      isPlainObject(fromVal)
                  ) {
                      mergeData(toVal, fromVal)
                  }
            }
            return to
          }
      
    2. 合并component directive filter

          export const ASSET_TYPES = [
              'component',
              'directive',
              'filter'
          ]
      
          // 合并逻辑
          function mergeAssets (
              parentVal: ?Object,
              childVal: ?Object,
              vm?: Component,
              key: string
          ): Object {
              // 创建一个空对象
              const res = Object.create(parentVal || null)
              if (childVal) {
                    process.env.NODE_ENV !== 'production' && assertObjectType(key, childVal, vm)
                    // 将子类的属性赋值到空对象中
                    return extend(res, childVal)
              } else {
                    return res
              }
          }
      
    3. 合并watch

        // 观察者哈希不能覆盖一个
        // 另一个,所以我们将它们合并为数组。
      
        strats.watch = function (
          parentVal: ?Object,
          childVal: ?Object,
          vm?: Component,
          key: string
        ): ?Object {
          // work around Firefox's Object.prototype.watch...
          // 由于火狐浏览器Gecko内核实现了watch方法,这个地方需要重置watch值
          if (parentVal === nativeWatch) parentVal = undefined
          if (childVal === nativeWatch) childVal = undefined
      
          // 如果子类不存在,直接返回深拷贝的父类
          if (!childVal) return Object.create(parentVal || null)
      
          // 判断子类是否是对象,不是则报出警告
          if (process.env.NODE_ENV !== 'production') {
            assertObjectType(key, childVal, vm)
          }
      
          // 父类不存在,则直接返回子类
          if (!parentVal) return childVal
      
          const ret = {}
      
          // 将父类的属性浅拷贝
          extend(ret, parentVal)
      
          //
            // 遍历子类的watch,如果子类存在相同的监听对象,
            // 就将监听回调合并到一个数组中,便于后面顺序执行,
            // parent.concat(child)
            //
          for (const key in childVal) {
            let parent = ret[key]
            const child = childVal[key]
            if (parent && !Array.isArray(parent)) {
              parent = [parent]
            }
            ret[key] = parent
              ? parent.concat(child)
              : Array.isArray(child) ? child : [child]
          }
      
          // 返回合并后的数据
          return ret
        }
      
    4. 其他合并策略 props methods inject computed

        /**
          * Other object hashes.
          */
          strats.props =
          strats.methods =
          strats.inject =
          strats.computed = function (
            parentVal: ?Object,
            childVal: ?Object,
            vm?: Component,
            key: string
          ): ?Object {
            // 判断值是否是对象
            if (childVal && process.env.NODE_ENV !== 'production') {
              assertObjectType(key, childVal, vm)
            }
      
            // 父类没有值,直接返回子类
            if (!parentVal) return childVal
      
            // 将父类的值赋值到空对象中
            const ret = Object.create(null)
            extend(ret, parentVal)
      
            // 合并子类的值到ret中,子类覆盖父类
            if (childVal) extend(ret, childVal)
            return ret
          }
      
    5. 合并生命周期

        export const LIFECYCLE_HOOKS = [
          'beforeCreate',
          'created',
          'beforeMount',
          'mounted',
          'beforeUpdate',
          'updated',
          'beforeDestroy',
          'destroyed',
          'activated',
          'deactivated',
          'errorCaptured',
          'serverPrefetch'
        ]
      
        // 遍历hook钩子,循环添加hook合并策略
        LIFECYCLE_HOOKS.forEach(hook => {
          strats[hook] = mergeHook
        })
      
        /**
          * Hooks and props are merged as arrays.
          * 将对应的hook回调合并成一个数组
          */
          function mergeHook (
            parentVal: ?Array<Function>,
            childVal: ?Function | ?Array<Function>
          ): ?Array<Function> {
            const res = childVal
              ? parentVal
                ? parentVal.concat(childVal)
                : Array.isArray(childVal)
                  ? childVal
                  : [childVal]
              : parentVal
            return res
              ? dedupeHooks(res)
              : res
          }
      
          function dedupeHooks (hooks) {
            const res = []
            for (let i = 0; i < hooks.length; i++) {
              if (res.indexOf(hooks[i]) === -1) {
                res.push(hooks[i])
              }
            }
            return res
          }
      
  5. 初始化生命周期
    initLifecycle(vm)

  export function initLifecycle (vm: Component) {
    const options = vm.$options

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