不过,即便如此,我们又产生了一个新的疑惑,这种语法我没见过,那么经过babel转换后的class会是什么样的呢?毕竟这个转换结果会作为参数传递 给Component装饰器来处理,要想了解Component的处理过程,这个参数需要先了解。
于是,我在Component函数内添加了一条console.log(),得到了打印后的结果,只是我使用的webpack+babel-loader执行的编译,结果比较难以阅读,我简单翻译了一下,并和class源码一起对比如下:
// 转换前 class User { name = 'yl' age = 10 get computeMethod () { cnsole.log(1) } method () { console.log(2) } } // 转换后 function User () { this.name = 'yl' this.age = 10 } // 计算属性定义 User.prototype.defineProperty(this, 'computeValue', { get () { console.log(1) return this.name } }) User.prototype.method = function () { console.log(2) }
由此,我们也可以推测出,一个.vue文件导出的类会被解析成什么样子。
装饰器函数又做了什么
此时,我们已经知晓了传递给装饰器函数的参数是什么样了。这个参数应该是一个构造函数,它的主体会对类实例的属性进行赋值,它的原型则携带着各种属性和方法。
而我们知道的,如果不使用vue-class-component,那么一个.vue文件应该导出如下对象:
export default { name: 'test', data () { return {...} }, computed: { com1 () {...}, com2 () {...} }, methods: {...}, // 各种hook函数 }
很显然,装饰器函数必然是将传入的组件构造函数转换成了一个vue配置对象。那么,具体内部是怎么做的呢?我们来看看源码。(源码笔者加上了详细注释,但较长,可以直接跳过看后面的总结。)
// 这个函数就是封装了装饰器逻辑的函数,接受两个参数: // 第一个是所装饰的类的构造函数;第二个是开发者传入的mixins对象 function componentFactory ( Component: VueClass<Vue>, options: ComponentOptions<Vue> = {} ): VueClass<Vue> { // 首先给options.name赋值,确保最终生成的对象具有name属性。 options.name = options.name || (Component as any)._componentTag || (Component as any).name // 获取构造函数原型,这个原型上挂在了该类的method const proto = Component.prototype // 遍历原型 Object.getOwnPropertyNames(proto).forEach(function (key) { // 如果是constructor,则不处理。 // 这也是为什么vue单文件组件类不需要constructor的直接原因,因为有也不会做任何处理 if (key === 'constructor') { return } // 如果原型属性(方法)名是vue生命周期钩子名,则直接作为钩子函数挂载在options最外层 if ($internalHooks.indexOf(key) > -1) { options[key] = proto[key] return } // 先获取到原型属性的descriptor。 // 在前文已提及,计算属性其实也是挂载在原型上的,所以需要对descriptor进行判断 const descriptor = Object.getOwnPropertyDescriptor(proto, key)! if (descriptor.value !== void 0) { // 如果属性值是一个function,则认为这是一个方法,挂载在methods下 if (typeof descriptor.value === 'function') { (options.methods || (options.methods = {}))[key] = descriptor.value } else { // 如果不是,则认为是一个普通的data属性。 // 但是这是原型上,所以更类似mixins,因此挂在mixins下。 (options.mixins || (options.mixins = [])).push({ data (this: Vue) { return { [key]: descriptor.value } } }) } } else if (descriptor.get || descriptor.set) { // 如果value是undefined(ps:void 0 === undefined)。 // 且描述符具有get或者set方法,则认为是计算属性。不理解的参考我上面关于class转换成构造函数的例子 // 这里可能和普通的计算属性不太一样,因为一般计算属性只是用来获取值的,但这里却有setter。 // 不过如果不使用setter,与非class方式开发无异,但有这一步处理,在某些场景会有特效。 (options.computed || (options.computed = {}))[key] = { get: descriptor.get, set: descriptor.set } } }) // 收集构造函数实例化对象的属性作为data,并放入mixins (options.mixins || (options.mixins = [])).push({ data (this: Vue) { // 实例化Component构造函数,并收集其自身的(非原型上的)属性导出,内部还针对不同vue版本做了兼容。 // 感兴趣的可以自己去瞅瞅源码,不复杂,在此不赘述。 return collectDataFromConstructor(this, Component) } }) // 处理属性装饰器,vue-class-component只提供了类装饰器。 // 像props、components等特殊参数只能写在Component(options)的options参数里。 // 通过这个接口可以扩展出属性装饰器,像vue-property-decorator库那种的属性装饰器 const decorators = (Component as DecoratedClass).__decorators__ if (decorators) { decorators.forEach(fn => fn(options)) delete (Component as DecoratedClass).__decorators__ } // 获取Vue对象 const superProto = Object.getPrototypeOf(Component.prototype) const Super = superProto instanceof Vue ? superProto.constructor as VueClass<Vue> : Vue // 通过vue.extend生成一个vue实例 const Extended = Super.extend(options) // 在前面只处理了Component构造函数原型和其实例化对象的属性和方法。 // 对于构造函数本身的静态属性还没有处理,在此处理,处理过程类似前面,不赘述。 forwardStaticMembers(Extended, Component, Super) // 反射相关处理,这个是新特性,本人了解也不多,但到此已经不影响理解了,所以可以略过。 // 如有对此了解的,欢迎补充。 if (reflectionIsSupported) { copyReflectionMetadata(Extended, Component) } // 最终返回这个vue实例对象 return Extended }
源码较长,在此总结一下。这里主要做了四件事:
第一,将传入的构造函数原型上的属性放入data中,将方法根据是否是生命周期钩子、是否是计算属性,来分别放入对应的位置。