怎么给 vue 定义全局的方法?
目前一般有两种做法:一是给直接给 Vue.prototype 上添加,另外一个是通过 Vue.mixin 注册。但是为什么 prototype 和 mixin 里面的方法可以在组件访问到呢?
源码分析 #
我们都知道 Vue 内部很多操作都是通过虚拟节点进行的,在初始化时候会执行创建虚拟节点的操作,这个就是通过一个叫 createElement 的函数进行的,就是渲染函数的第一个参数,在 createElement 内如果发现一个节点是组件的话,会执行 createComponent 函数
export function createComponent(
Ctor: Class<Component> | Function | Object | void,
data: ?VNodeData,
context: Component,
children: ?Array<VNode>,
tag?: string
): VNode | Array<VNode> | void {
if (isUndef(Ctor)) {
return;
}
//省略其他逻辑...
// ......
const baseCtor = context.$options._base;
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor);
}
// ......
}
export function createComponent(
Ctor: Class<Component> | Function | Object | void,
data: ?VNodeData,
context: Component,
children: ?Array<VNode>,
tag?: string
): VNode | Array<VNode> | void {
if (isUndef(Ctor)) {
return;
}
//省略其他逻辑...
// ......
const baseCtor = context.$options._base;
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor);
}
// ......
}
这里是创建了一个组件的构造函数,baseCtor 是 Vue 的构造函数,其实下面执行的就是 Vue.extend()方法,这个方法是 Vue 构造函数上的一个静态方法,应该有不少小伙伴都用过这个方法,我们来看下这个方法做了什么事情
/**
* Class inheritance
*/
Vue.extend = function (extendOptions: Object): Function {
//省略其他逻辑...
// ......
const 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);
// ......
};
/**
* Class inheritance
*/
Vue.extend = function (extendOptions: Object): Function {
//省略其他逻辑...
// ......
const 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 函数,并且继承了将 prototype 指向了 Object.create(Super.prototype),是 js 里一个典型的继承方法,要知道,最终组件的实例化是通过这个 Sub 构造函数进行的,在组件实例内访问一个属性的时候,如果本实例上没有的话,会通过原型链向上去查找,这样我们就可以在组件内部访问到 Vue 的原型。
那么 mixin 是怎么实现的呢?其实上面这段代码还有一个是 mergeOptions 的操作,这个 mergeOptions 函数做的事情是将两个 options 合并在一起,这里就不展开说了,因为里面的东西比较多。这里其实就是把 Super.options 和我们传入的 options 合并在一起,这个 Super 的 options 其实也就是 Vue 的 options,在我们使用 Vue.mixin 这个方法的时候,会把我们传入的 options 添加到 Vue.options 上
export function initMixin(Vue: GlobalAPI) {
Vue.mixin = function (mixin: Object) {
this.options = mergeOptions(this.options, mixin);
return this;
};
}
export function initMixin(Vue: GlobalAPI) {
Vue.mixin = function (mixin: Object) {
this.options = mergeOptions(this.options, mixin);
return this;
};
}
这样 Vue.options 上就会有我们添加到属性了,在 extend 的时候这个属性也会扩展到组件构造函数的 options 上,
然后在组件初始化的时候,会执行 init 方法:
Vue.prototype._init = function (options?: Object) {
//省略其他逻辑
// ......
// 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
);
}
// ......
};
Vue.prototype._init = function (options?: Object) {
//省略其他逻辑
// ......
// 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
);
}
// ......
};
里面判断到是组件时会执行 initInternalComponent 这个方法
export function initInternalComponent(
vm: Component,
options: InternalComponentOptions
) {
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; // 父Vnode,activeInstance
opts._parentVnode = parentVnode; // 占位符Vnode
opts._parentElm = options._parentElm;
opts._refElm = options._refElm;
const vnodeComponentOptions = parentVnode.componentOptions; // componentOptions是createComponent时候传入new Vnode()的
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;
}
}
export function initInternalComponent(
vm: Component,
options: InternalComponentOptions
) {
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; // 父Vnode,activeInstance
opts._parentVnode = parentVnode; // 占位符Vnode
opts._parentElm = options._parentElm;
opts._refElm = options._refElm;
const vnodeComponentOptions = parentVnode.componentOptions; // componentOptions是createComponent时候传入new Vnode()的
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;
}
}
这里又通过 const opts = vm.$options = Object.create(vm.constructor.options) 将组件构造函数的 options 赋值给了 vm.$options,这里的 vm.constructor.options 就是刚才和 Vue.options 合并后的组件构造函数上的 options,这样我们就在组件内部拿到了 Vue.mixin 定义的方法
版权属于: vincent
转载时须注明出处及本声明