怎么给 vue 定义全局的方法?
给 vue 定义全局的方法
目前一般有两种做法:一是给直接给 Vue.prototype 上添加,另外一个是通过 Vue.mixin 注册。但是为什么 prototype 和 mixin 里面的方法可以在组件访问到呢?
源码分析
我们都知道 Vue 内部很多操作都是通过虚拟节点进行的,在初始化时候会执行创建虚拟节点的操作,这个就是通过一个叫 createElement 的函数进行的,就是渲染函数的第一个参数,在 createElement 内如果发现一个节点是组件的话,会执行 createComponent 函数
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 构造函数上的一个静态方法,应该有不少小伙伴都用过这个方法,我们来看下这个方法做了什么事情
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 上
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 方法:
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 这个方法
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 定义的方法