·vincent

怎么给 vue 定义全局的方法?

给 vue 定义全局的方法

Javascript

目前一般有两种做法:一是给直接给 Vue.prototype 上添加,另外一个是通过 Vue.mixin 注册。但是为什么 prototype 和 mixin 里面的方法可以在组件访问到呢?

源码分析

我们都知道 Vue 内部很多操作都是通过虚拟节点进行的,在初始化时候会执行创建虚拟节点的操作,这个就是通过一个叫 createElement 的函数进行的,就是渲染函数的第一个参数,在 createElement 内如果发现一个节点是组件的话,会执行 createComponent 函数

js
1export function createComponent(
2  Ctor: Class<Component> | Function | Object | void,
3  data: ?VNodeData,
4  context: Component,
5  children: ?Array<VNode>,
6  tag?: string
7): VNode | Array<VNode> | void {
8  if (isUndef(Ctor)) {
9    return;
10  }
11
12  //省略其他逻辑...
13  // ......
14  const baseCtor = context.$options._base;
15  if (isObject(Ctor)) {
16    Ctor = baseCtor.extend(Ctor);
17  }
18
19  // ......
20}

这里是创建了一个组件的构造函数,baseCtor 是 Vue 的构造函数,其实下面执行的就是 Vue.extend()方法,这个方法是 Vue 构造函数上的一个静态方法,应该有不少小伙伴都用过这个方法,我们来看下这个方法做了什么事情

js
1/**
2 * Class inheritance
3 */
4Vue.extend = function (extendOptions: Object): Function {
5  //省略其他逻辑...
6  // ......
7  const Sub = function VueComponent(options) {
8    this._init(options);
9  };
10  Sub.prototype = Object.create(Super.prototype);
11  Sub.prototype.constructor = Sub;
12  Sub.cid = cid++;
13  Sub.options = mergeOptions(Super.options, extendOptions);
14  // ......
15};

创建了一个 Sub 函数,并且继承了将 prototype 指向了 Object.create(Super.prototype),是 js 里一个典型的继承方法,要知道,最终组件的实例化是通过这个 Sub 构造函数进行的,在组件实例内访问一个属性的时候,如果本实例上没有的话,会通过原型链向上去查找,这样我们就可以在组件内部访问到 Vue 的原型。

那么 mixin 是怎么实现的呢?其实上面这段代码还有一个是 mergeOptions 的操作,这个 mergeOptions 函数做的事情是将两个 options 合并在一起,这里就不展开说了,因为里面的东西比较多。这里其实就是把 Super.options 和我们传入的 options 合并在一起,这个 Super 的 options 其实也就是 Vue 的 options,在我们使用 Vue.mixin 这个方法的时候,会把我们传入的 options 添加到 Vue.options 上

js
1export function initMixin(Vue: GlobalAPI) {
2  Vue.mixin = function (mixin: Object) {
3    this.options = mergeOptions(this.options, mixin);
4    return this;
5  };
6}

这样 Vue.options 上就会有我们添加到属性了,在 extend 的时候这个属性也会扩展到组件构造函数的 options 上,

然后在组件初始化的时候,会执行 init 方法:

js
1Vue.prototype._init = function (options?: Object) {
2  //省略其他逻辑
3  // ......
4  // merge options
5  if (options && options._isComponent) {
6    // optimize internal component instantiation
7    // since dynamic options merging is pretty slow, and none of the
8    // internal component options needs special treatment.
9    initInternalComponent(vm, options);
10  } else {
11    vm.$options = mergeOptions(
12      resolveConstructorOptions(vm.constructor),
13      options || {},
14      vm
15    );
16  }
17  // ......
18};

里面判断到是组件时会执行 initInternalComponent 这个方法

js
1export function initInternalComponent(
2  vm: Component,
3  options: InternalComponentOptions
4) {
5  const opts = (vm.$options = Object.create(vm.constructor.options));
6  // doing this because it's faster than dynamic enumeration.
7  const parentVnode = options._parentVnode;
8  opts.parent = options.parent; // 父Vnode,activeInstance
9  opts._parentVnode = parentVnode; // 占位符Vnode
10  opts._parentElm = options._parentElm;
11  opts._refElm = options._refElm;
12
13  const vnodeComponentOptions = parentVnode.componentOptions; // componentOptions是createComponent时候传入new Vnode()的
14  opts.propsData = vnodeComponentOptions.propsData;
15  opts._parentListeners = vnodeComponentOptions.listeners;
16  opts._renderChildren = vnodeComponentOptions.children;
17  opts._componentTag = vnodeComponentOptions.tag;
18
19  if (options.render) {
20    opts.render = options.render;
21    opts.staticRenderFns = options.staticRenderFns;
22  }
23}

这里又通过 const opts = vm.$options = Object.create(vm.constructor.options) 将组件构造函数的 options 赋值给了 vm.$options,这里的 vm.constructor.options 就是刚才和 Vue.options 合并后的组件构造函数上的 options,这样我们就在组件内部拿到了 Vue.mixin 定义的方法