vue-class-component是vue作者尤大推出的一个支持使用class方式来开发vue单文件组件的库。但是,在使用过程中我却发现了几个奇怪的地方。
首先,我们看一个简单的使用例子:
// App.vue <script> import Vue from 'vue' import Component from 'vue-class-component' @Component({ props: { propMessage: String } }) export default class App extends Vue { // initial data msg=123 // use prop values for initial data helloMsg='Hello, '+this.propMessage // lifecycle hook mounted () { this.greet() } // computed get computedMsg () { return'computed '+this.msg } // method greet () { alert('greeting: '+this.msg) } } </script> //main.js import App from './App.vue' newVue({ el: '#app', router, store, components: { App }, template: '<App/>' })
在这个例子中,很容易发现几个疑点:
1. App类居然没有constructor构造函数;
2. 导出的类居然没有被new就直接使用了。
3. msg=123,这是什么语法?
首先,针对前两个疑问,需要说明一下,class不一定非得有构造函数,同样也不一定非得使用new才能使用。熟悉原理的朋友应该知道,class只是一个ES6的语法糖,说白了还是一个Function而已。但是,这两点无疑是class这个语法糖的重要价值所在,可这里却偏偏没用,不由让人奇怪,甚至会想,既然不当class用,那为什么不干脆就用Function呢?
而第三点,却是妥妥点的语法错误啊,为此我还特意打开了Chrome控制台试验了一下,确实报错了。实验结果如下:
那这到底是怎么回事呢?出于程序员的好奇心,我对vue-class-component的源码探索了一番。下面就一起来看看,相信看完就可以解答上面的疑惑了。
第一步,在看源码之前,必须对装饰器的知识有一定了解。装饰器种类有好几种,vue-class-component中主要用了类装饰器,本文只对类装饰器做简单介绍,更多信息请参阅阮老师的文章:。
类装饰器,顾名思义,就是用来装饰一个类的,说的直白点就是用于修改一个类的。它具体有两种用法。如下:
// 用法一 function Decorator (target) { // 处理target return target } @Decorator class ClassTest () {} // 用法二 function DecoratorFactory (options) { return function Decorator (target) { //@todo 利用options一起处理target // 然后返回 return target } } @DecoratorFctory(options) class ClassTest () {}
在两个用法中,我们将Decorator称为装饰器函数,DecoratorFactory称为装饰器工厂。
类装饰器函数规定只能接收类构造函数本身,如果还需要额外的参数传入,则需要使用装饰器工厂函数。
我们以装饰器工厂函数为例,说明其执行流程:
1. JS引擎首先会执行工厂函数,然后保存其返回的装饰器函数;
2. 然后解析class,将其转化为一个构造函数;
3. 将上述构造函数作为参数执行第一步得到的装饰器函数。
4. 如果装饰器函数有返回值,则会将类变量(如例子中的ClassTest变量)指向返回值,否则类变量仍然指向构造函数,基于JS引用变量的特点,即使仍指向原构造函数,这个构造函数也可能在装饰器中被改造过了。
直接使用装饰器函数的情况类似上面,只是少了装饰器工厂这一步处理过程。
了解了基本知识,我们开始第二步,解析vue-class-component执行流程。这里将根据装饰器的执行流程,分三个部分讲解。第一,工厂函数做了什么;第二,class解析之后是什么样的;第三,装饰器函数又做了什么。
工厂函数做了什么?
// vue-class-component使用的是TS语法 // Component实际上是既作为工厂函数,又作为装饰器函数 function Component (options: ComponentOptions<Vue> | VueClass<Vue>): any { if (typeofoptions==='function') { // 区别一下。这里的命名虽然是工厂,其实它才是真正封装装饰器逻辑的函数 return componentFactory (options) } return function (Component:VueClass<Vue>){ return componentFactory(Component,options) } }
从源码中可以看出,Component函数只是对参数进行了判断,说明它既可以用作工厂函数,也可以用作装饰器函数。而实际装饰器的逻辑则被封装在componentFactory函数里,这里对命名需要注意区分下,此工厂非彼工厂。
Class解析之后是什么样的
在文章开头我们就有疑问,在class中不经过constructor直接给其属性赋值是不符合JS语法的,而且我们还在Chrome上试验过了,确实会报错。但我们在使用component-class-component时却又实实在在那么干了,并且也没什么问题,这是怎么回事呢?
事实上,Chrome等主流浏览器对于ES6以及更高级的ES7、ES8的支持是不完整的,很多功能特性都不支持,这也是我们平时为什么都会使用babel来将高级的ES语法转换成ES5的原因。而我们前面提及的这点疑惑正是这个原因,Chrome不支持,不代表babel不支持。