new vue 方法参数_vue进阶之路 —— 详解vue2.x初始化流程

本文深入探讨Vue2.x组件实例化的流程,从创建HTML到初始化Vue,详细分析Vue实例的_options如何形成,特别是data对象如何转换成函数。通过源码解读,揭示Vue的mergeOptions函数在处理组件选项时的角色,以及optionMergeStrategies如何实现自定义策略处理,确保data的正确合并。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

如果要成长为一名高级乃至资深前端工程师,不但要熟练使用,还要明白背后的原理,达到举一反三的程度。在整个成长过程中,阅读源码是不可或缺的重要一环。

本篇文章以vue 2.6.10源码为例,为大家分享vue2.x组件实例化流程。

首先创建一个index.html文件,并且引入vue2.6.10.js

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Hello Vue</title>
</head>
<body>
    <div id="app">
        <div>{{a}}</div>
    </div>
<script type="text/javascript" src="vue_2.6.10.js"></script>
<script>
    var vm = new Vue({
        el: "#app",
        data: { 
           a: 1
        }
    });
    console.log(vm.$options);
</script>
</body>
</html>

控制台输出选项参数为

795fe77e4898834005f4530c981c29d1.png

可以看到除了我们初始化时传递进去的el和data参数,还多了好多其他的参数,例如components,directives等。特别需要注意的是,传进去的data是一个对象,但是却变成了一个函数。

这是什么原因呢?

下面从Vue实例初始化流程来为大家具体分析,内容有点多,还希望大家能够耐心看完哦。

在vue2.6.10.js中首先检测宿主环境是否支持匹配条件

(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  typeof define === 'function' && define.amd ? define(factory) :
  (global = global || self, global.Vue = factory());
}(this, function () { 'use strict';

然后定义Vue工厂函数factory(),并且赋值给global.Vue,首先检测是不是通过new Vue()来初始化根实例,如果直接在index.html中把Vue()当作一个普通函数来调用,则会发出警告;options就是在index.html中new Vue()时传递的选项参数。

 function Vue (options) {
    if (!(this instanceof Vue)
    ) {
      warn('Vue is a constructor and should be called with the `new` keyword');
    }
    this._init(options);
  }

调用this._init(options)对选项参数做处理,通过函数initMixin(Vue)来完成。

function initMixin (Vue) {
    // 定义Vue的原型属性_init
    Vue.prototype._init = function (options) {
      var vm = this;
      // a uid
      vm._uid = uid$3++;

      var startTag, endTag;
      /* istanbul ignore if */
      if (config.performance && mark) {
        startTag = "vue-perf-start:" + (vm._uid);
        endTag = "vue-perf-end:" + (vm._uid);
        mark(startTag);
      }

      // a flag to avoid this being observed
      vm._isVue = true;
      // merge 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
        );
      }
      /* istanbul ignore else */
      {
        initProxy(vm);
      }
      // expose real self
      vm._self = vm;
      initLifecycle(vm);
      initEvents(vm);
      initRender(vm);
      callHook(vm, 'beforeCreate');
      initInjections(vm); // resolve injections before data/props
      initState(vm);
      initProvide(vm); // resolve provide after data/props
      callHook(vm, 'created');

      /* istanbul ignore if */
      if (config.performance && mark) {
        vm._name = formatComponentName(vm, false);
        mark(endTag);
        measure(("vue " + (vm._name) + " init"), startTag, endTag);
      }

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

在该函数中,定义Vue的原型属性_init,调用mergeOptions对options参数做处理

vm.$options = mergeOptions(
    resolveConstructorOptions(vm.constructor),
    options || {},
    vm
);

resolveConstructorOptions函数定义如下,传递参数vm.constructor就是Vue构造函数。所以Ctor等价于Vue,Ctor.super为false。说明该函数不是给Vue根实例用的,而是给Vue子类构造函数用的。对于Vue父类,直接返回父类的options。

function resolveConstructorOptions (Ctor) {
    var options = Ctor.options;
    if (Ctor.super) {
      var superOptions = resolveConstructorOptions(Ctor.super);
      var 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)
        var 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
  }

那么Vue根实例的子组件options参数怎么处理呢?通过Vue.extend来进行处理

Vue.extend = function (extendOptions) {
      extendOptions = extendOptions || {};
      var Super = this;
      var SuperId = Super.cid;
      var cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {});
      if (cachedCtors[SuperId]) {
        return cachedCtors[SuperId]
      }

      var name = extendOptions.name || Super.options.name;
      if (name) {
        validateComponentName(name);
      }

      var 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.
      if (Sub.options.props) {
        initProps$1(Sub);
      }
      if (Sub.options.computed) {
        initComputed$1(Sub);
      }

      // allow further extension/mixin/plugin usage
      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
    };
  }

可以看到上述函数也调用了mergeOptions,说明mergeOptions 非常重要,根实例和子组件的options处理都会调用它。

Vue父类的options通过调用函数initGlobalAPI(Vue)来进行处理,将ASSET_TYPES数组的每一项添加进Vue.options,然后再调用mergeOptions进行合并。

 Vue.options = Object.create(null);
    .forEach(function (type) {
      Vue.options[type + 's'] = Object.create(null);
    });

ASSET_TYPES数组定义如下,至此回答了为什么控制台输出的Vue options会多出好多项。那为什么要在Vue.options的每一项key值中加's',不直接在ASSET_TYPES数组中进行添加呢?因为ASSET_TYPES在好多地方会复用。

 var ASSET_TYPES = [
    'component',
    'directive',
    'filter'
  ];

还有一个遗留问题:输入的data是一个对象,为什么输出的data变成了一个函数?

因为vue2.5.x中引入了选项的策略处理

接下来我们分析mergeOptions函数,其中运用了选项的策略处理,本质就是通过javascript增强配置选项的表达力。首先遍历父组件的key值,然后遍历子组件,如果父子组件中有相同的key值,则只调用mergeField处理一次。

function mergeOptions (
    parent,
    child,
    vm
  ) {
    {
      checkComponents(child);
    }

    // ...

    var options = {};
    var key;
    for (key in parent) {
      mergeField(key);
    }
    for (key in child) {
      if (!hasOwn(parent, key)) {
        mergeField(key);
      }
    }
    function mergeField (key) {
      var strat = strats[key] || defaultStrat;
      options[key] = strat(parent[key], child[key], vm, key);
    }
    return options
  }

接下来看strats相关的定义

var strats = config.optionMergeStrategies;

config是一个全局的配置对象,config.optionMergeStrategies默认为一个空对象

 var config = ({
    /**
     * Option merge strategies (used in core/util/options)
     */
    // $flow-disable-line
    optionMergeStrategies: Object.create(null),
    // ...
    });

strats.data被定义为一个函数,最终返回函数mergedInstanceDataFn,这就解释为什么我们输入一个data对象,而控制台输出函数的问题了。

strats.data = function (
    parentVal,
    childVal,
    vm
  ) {
    if (!vm) {
      if (childVal && typeof childVal !== 'function') {
        warn(
          'The "data" option should be a function ' +
          'that returns a per-instance value in component ' +
          'definitions.',
          vm
        );

        return parentVal
      }
      return mergeDataOrFn(parentVal, childVal)
    }

    return mergeDataOrFn(parentVal, childVal, vm)
  };
function mergeDataOrFn (
    parentVal,
    childVal,
    vm
  ) {
    if (!vm) {
      // in a Vue.extend merge, both should be functions
      if (!childVal) {
        return parentVal
      }
      if (!parentVal) {
        return childVal
      }
      // when parentVal & childVal are both present,
      // we need to return a function that returns the
      // merged result of both functions... no need to
      // check if parentVal is a function here because
      // it has to be a function to pass previous merges.
      return function mergedDataFn () {
        return mergeData(
          typeof childVal === 'function' ? childVal.call(this, this) : childVal,
          typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal
        )
      }
    } else {
      return function mergedInstanceDataFn () {
        // instance merge
        var instanceData = typeof childVal === 'function'
          ? childVal.call(vm, vm)
          : childVal;
        var defaultData = typeof parentVal === 'function'
          ? parentVal.call(vm, vm)
          : parentVal;
        if (instanceData) {
          return mergeData(instanceData, defaultData)
        } else {
          return defaultData
        }
      }
    }
  }

接下来接着讲自定义策略处理, initGlobalAPI(Vue)函数除了扩展静态属性,还对Vue的全局配置config设了一个拦截,表明用户只能读取congfig的值,而不能修改它。

  // config
    var configDef = {};
    configDef.get = function () { return config; };
    {
      configDef.set = function () {
        warn(
          'Do not replace the Vue.config object, set individual fields instead.'
        );
      };
    }
    Object.defineProperty(Vue, 'config', configDef);

那我们怎么实现自定义策略处理呢?需要借助Vue.config.optionMergeStrategies,验证如下

<script>
     Vue.config.optionMergeStrategies.count = function(partentVal, childVal, vm) {
        return childVal >= 100 ? 100 : childVal;
    };
    var vm = new Vue({
        el: "#app",
        data: { 
           a: 1
        },
        count: 99
    });
    console.log(vm.$options);
</script>

控制台输出如下

当count赋值99时,输出99

b43b0218d97b888f535b783457698689.png

当count赋值101时,输出100

8a9d974ced8f7a32b45131625e57055a.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值