vue-class-component是vue作者尤大推出的一個(gè)支持使用class方式來(lái)開(kāi)發(fā)vue單文件組件的庫(kù)。但是,在使用過(guò)程中我卻發(fā)現(xiàn)了幾個(gè)奇怪的地方。
睢陽(yáng)網(wǎng)站建設(shè)公司創(chuàng)新互聯(lián)建站,睢陽(yáng)網(wǎng)站設(shè)計(jì)制作,有大型網(wǎng)站制作公司豐富經(jīng)驗(yàn)。已為睢陽(yáng)近1000家提供企業(yè)網(wǎng)站建設(shè)服務(wù)。企業(yè)網(wǎng)站搭建\成都外貿(mào)網(wǎng)站建設(shè)要多少錢,請(qǐng)找那個(gè)售后服務(wù)好的睢陽(yáng)做網(wǎng)站的公司定做!
首先,我們看一個(gè)簡(jiǎn)單的使用例子:
// 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/>'
})在這個(gè)例子中,很容易發(fā)現(xiàn)幾個(gè)疑點(diǎn):
1. App類居然沒(méi)有constructor構(gòu)造函數(shù);
2. 導(dǎo)出的類居然沒(méi)有被new就直接使用了。
3. msg=123,這是什么語(yǔ)法?
首先,針對(duì)前兩個(gè)疑問(wèn),需要說(shuō)明一下,class不一定非得有構(gòu)造函數(shù),同樣也不一定非得使用new才能使用。熟悉原理的朋友應(yīng)該知道,class只是一個(gè)ES6的語(yǔ)法糖,說(shuō)白了還是一個(gè)Function而已。但是,這兩點(diǎn)無(wú)疑是class這個(gè)語(yǔ)法糖的重要價(jià)值所在,可這里卻偏偏沒(méi)用,不由讓人奇怪,甚至?xí)耄热徊划?dāng)class用,那為什么不干脆就用Function呢?
而第三點(diǎn),卻是妥妥點(diǎn)的語(yǔ)法錯(cuò)誤啊,為此我還特意打開(kāi)了Chrome控制臺(tái)試驗(yàn)了一下,確實(shí)報(bào)錯(cuò)了。實(shí)驗(yàn)結(jié)果如下:

那這到底是怎么回事呢?出于程序員的好奇心,我對(duì)vue-class-component的源碼探索了一番。下面就一起來(lái)看看,相信看完就可以解答上面的疑惑了。
第一步,在看源碼之前,必須對(duì)裝飾器的知識(shí)有一定了解。裝飾器種類有好幾種,vue-class-component中主要用了類裝飾器,本文只對(duì)類裝飾器做簡(jiǎn)單介紹,更多信息請(qǐng)參閱阮老師的文章:ECMAScript 6入門。
類裝飾器,顧名思義,就是用來(lái)裝飾一個(gè)類的,說(shuō)的直白點(diǎn)就是用于修改一個(gè)類的。它具體有兩種用法。如下:
// 用法一
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 () {}
在兩個(gè)用法中,我們將Decorator稱為裝飾器函數(shù),DecoratorFactory稱為裝飾器工廠。
類裝飾器函數(shù)規(guī)定只能接收類構(gòu)造函數(shù)本身,如果還需要額外的參數(shù)傳入,則需要使用裝飾器工廠函數(shù)。
我們以裝飾器工廠函數(shù)為例,說(shuō)明其執(zhí)行流程:
1. JS引擎首先會(huì)執(zhí)行工廠函數(shù),然后保存其返回的裝飾器函數(shù);
2. 然后解析class,將其轉(zhuǎn)化為一個(gè)構(gòu)造函數(shù);
3. 將上述構(gòu)造函數(shù)作為參數(shù)執(zhí)行第一步得到的裝飾器函數(shù)。
4. 如果裝飾器函數(shù)有返回值,則會(huì)將類變量(如例子中的ClassTest變量)指向返回值,否則類變量仍然指向構(gòu)造函數(shù),基于JS引用變量的特點(diǎn),即使仍指向原構(gòu)造函數(shù),這個(gè)構(gòu)造函數(shù)也可能在裝飾器中被改造過(guò)了。
直接使用裝飾器函數(shù)的情況類似上面,只是少了裝飾器工廠這一步處理過(guò)程。
了解了基本知識(shí),我們開(kāi)始第二步,解析vue-class-component執(zhí)行流程。這里將根據(jù)裝飾器的執(zhí)行流程,分三個(gè)部分講解。第一,工廠函數(shù)做了什么;第二,class解析之后是什么樣的;第三,裝飾器函數(shù)又做了什么。
工廠函數(shù)做了什么?
// vue-class-component使用的是TS語(yǔ)法
// Component實(shí)際上是既作為工廠函數(shù),又作為裝飾器函數(shù)
function Component (options: ComponentOptions<Vue> | VueClass<Vue>): any {
if (typeofoptions==='function') {
// 區(qū)別一下。這里的命名雖然是工廠,其實(shí)它才是真正封裝裝飾器邏輯的函數(shù)
return componentFactory (options)
}
return function (Component:VueClass<Vue>){
return componentFactory(Component,options)
}
}
從源碼中可以看出,Component函數(shù)只是對(duì)參數(shù)進(jìn)行了判斷,說(shuō)明它既可以用作工廠函數(shù),也可以用作裝飾器函數(shù)。而實(shí)際裝飾器的邏輯則被封裝在componentFactory函數(shù)里,這里對(duì)命名需要注意區(qū)分下,此工廠非彼工廠。
Class解析之后是什么樣的
在文章開(kāi)頭我們就有疑問(wèn),在class中不經(jīng)過(guò)constructor直接給其屬性賦值是不符合JS語(yǔ)法的,而且我們還在Chrome上試驗(yàn)過(guò)了,確實(shí)會(huì)報(bào)錯(cuò)。但我們?cè)谑褂胏omponent-class-component時(shí)卻又實(shí)實(shí)在在那么干了,并且也沒(méi)什么問(wèn)題,這是怎么回事呢?
事實(shí)上,Chrome等主流瀏覽器對(duì)于ES6以及更高級(jí)的ES7、ES8的支持是不完整的,很多功能特性都不支持,這也是我們平時(shí)為什么都會(huì)使用babel來(lái)將高級(jí)的ES語(yǔ)法轉(zhuǎn)換成ES5的原因。而我們前面提及的這點(diǎn)疑惑正是這個(gè)原因,Chrome不支持,不代表babel不支持。
不過(guò),即便如此,我們又產(chǎn)生了一個(gè)新的疑惑,這種語(yǔ)法我沒(méi)見(jiàn)過(guò),那么經(jīng)過(guò)babel轉(zhuǎn)換后的class會(huì)是什么樣的呢?畢竟這個(gè)轉(zhuǎn)換結(jié)果會(huì)作為參數(shù)傳遞 給Component裝飾器來(lái)處理,要想了解Component的處理過(guò)程,這個(gè)參數(shù)需要先了解。
于是,我在Component函數(shù)內(nèi)添加了一條console.log(),得到了打印后的結(jié)果,只是我使用的webpack+babel-loader執(zhí)行的編譯,結(jié)果比較難以閱讀,我簡(jiǎn)單翻譯了一下,并和class源碼一起對(duì)比如下:
// 轉(zhuǎn)換前
class User {
name = 'yl'
age = 10
get computeMethod () {
cnsole.log(1)
}
method () {
console.log(2)
}
}
// 轉(zhuǎn)換后
function User () {
this.name = 'yl'
this.age = 10
}
// 計(jì)算屬性定義
User.prototype.defineProperty(this, 'computeValue', {
get () {
console.log(1)
return this.name
}
})
User.prototype.method = function () {
console.log(2)
}由此,我們也可以推測(cè)出,一個(gè).vue文件導(dǎo)出的類會(huì)被解析成什么樣子。
裝飾器函數(shù)又做了什么
此時(shí),我們已經(jīng)知曉了傳遞給裝飾器函數(shù)的參數(shù)是什么樣了。這個(gè)參數(shù)應(yīng)該是一個(gè)構(gòu)造函數(shù),它的主體會(huì)對(duì)類實(shí)例的屬性進(jìn)行賦值,它的原型則攜帶著各種屬性和方法。
而我們知道的,如果不使用vue-class-component,那么一個(gè).vue文件應(yīng)該導(dǎo)出如下對(duì)象:
export default {
name: 'test',
data () {
return {...}
},
computed: {
com1 () {...},
com2 () {...}
},
methods: {...},
// 各種hook函數(shù)
}
很顯然,裝飾器函數(shù)必然是將傳入的組件構(gòu)造函數(shù)轉(zhuǎn)換成了一個(gè)vue配置對(duì)象。那么,具體內(nèi)部是怎么做的呢?我們來(lái)看看源碼。(源碼筆者加上了詳細(xì)注釋,但較長(zhǎng),可以直接跳過(guò)看后面的總結(jié)。)
// 這個(gè)函數(shù)就是封裝了裝飾器邏輯的函數(shù),接受兩個(gè)參數(shù):
// 第一個(gè)是所裝飾的類的構(gòu)造函數(shù);第二個(gè)是開(kāi)發(fā)者傳入的mixins對(duì)象
function componentFactory (
Component: VueClass<Vue>,
options: ComponentOptions<Vue> = {}
): VueClass<Vue> {
// 首先給options.name賦值,確保最終生成的對(duì)象具有name屬性。
options.name = options.name || (Component as any)._componentTag || (Component as any).name
// 獲取構(gòu)造函數(shù)原型,這個(gè)原型上掛在了該類的method
const proto = Component.prototype
// 遍歷原型
Object.getOwnPropertyNames(proto).forEach(function (key) {
// 如果是constructor,則不處理。
// 這也是為什么vue單文件組件類不需要constructor的直接原因,因?yàn)橛幸膊粫?huì)做任何處理
if (key === 'constructor') {
return
}
// 如果原型屬性(方法)名是vue生命周期鉤子名,則直接作為鉤子函數(shù)掛載在options最外層
if ($internalHooks.indexOf(key) > -1) {
options[key] = proto[key]
return
}
// 先獲取到原型屬性的descriptor。
// 在前文已提及,計(jì)算屬性其實(shí)也是掛載在原型上的,所以需要對(duì)descriptor進(jìn)行判斷
const descriptor = Object.getOwnPropertyDescriptor(proto, key)!
if (descriptor.value !== void 0) {
// 如果屬性值是一個(gè)function,則認(rèn)為這是一個(gè)方法,掛載在methods下
if (typeof descriptor.value === 'function') {
(options.methods || (options.methods = {}))[key] = descriptor.value
} else {
// 如果不是,則認(rèn)為是一個(gè)普通的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)。
// 且描述符具有g(shù)et或者set方法,則認(rèn)為是計(jì)算屬性。不理解的參考我上面關(guān)于class轉(zhuǎn)換成構(gòu)造函數(shù)的例子
// 這里可能和普通的計(jì)算屬性不太一樣,因?yàn)橐话阌?jì)算屬性只是用來(lái)獲取值的,但這里卻有setter。
// 不過(guò)如果不使用setter,與非class方式開(kāi)發(fā)無(wú)異,但有這一步處理,在某些場(chǎng)景會(huì)有特效。
(options.computed || (options.computed = {}))[key] = {
get: descriptor.get,
set: descriptor.set
}
}
})
// 收集構(gòu)造函數(shù)實(shí)例化對(duì)象的屬性作為data,并放入mixins
(options.mixins || (options.mixins = [])).push({
data (this: Vue) {
// 實(shí)例化Component構(gòu)造函數(shù),并收集其自身的(非原型上的)屬性導(dǎo)出,內(nèi)部還針對(duì)不同vue版本做了兼容。
// 感興趣的可以自己去瞅瞅源碼,不復(fù)雜,在此不贅述。
return collectDataFromConstructor(this, Component)
}
})
// 處理屬性裝飾器,vue-class-component只提供了類裝飾器。
// 像props、components等特殊參數(shù)只能寫(xiě)在Component(options)的options參數(shù)里。
// 通過(guò)這個(gè)接口可以擴(kuò)展出屬性裝飾器,像vue-property-decorator庫(kù)那種的屬性裝飾器
const decorators = (Component as DecoratedClass).__decorators__
if (decorators) {
decorators.forEach(fn => fn(options))
delete (Component as DecoratedClass).__decorators__
}
// 獲取Vue對(duì)象
const superProto = Object.getPrototypeOf(Component.prototype)
const Super = superProto instanceof Vue
? superProto.constructor as VueClass<Vue>
: Vue
// 通過(guò)vue.extend生成一個(gè)vue實(shí)例
const Extended = Super.extend(options)
// 在前面只處理了Component構(gòu)造函數(shù)原型和其實(shí)例化對(duì)象的屬性和方法。
// 對(duì)于構(gòu)造函數(shù)本身的靜態(tài)屬性還沒(méi)有處理,在此處理,處理過(guò)程類似前面,不贅述。
forwardStaticMembers(Extended, Component, Super)
// 反射相關(guān)處理,這個(gè)是新特性,本人了解也不多,但到此已經(jīng)不影響理解了,所以可以略過(guò)。
// 如有對(duì)此了解的,歡迎補(bǔ)充。
if (reflectionIsSupported) {
copyReflectionMetadata(Extended, Component)
}
// 最終返回這個(gè)vue實(shí)例對(duì)象
return Extended
}源碼較長(zhǎng),在此總結(jié)一下。這里主要做了四件事:
第一,將傳入的構(gòu)造函數(shù)原型上的屬性放入data中,將方法根據(jù)是否是生命周期鉤子、是否是計(jì)算屬性,來(lái)分別放入對(duì)應(yīng)的位置。
第二,實(shí)例化構(gòu)造函數(shù),將構(gòu)造函數(shù)實(shí)例化對(duì)象的屬性放入data,實(shí)例化對(duì)象本身(不算原型上的)是不帶有方法的,即使某個(gè)屬性的值是function類型,也應(yīng)該作為data來(lái)處理。
第三、對(duì)構(gòu)造函數(shù)自身的靜態(tài)屬性和方法處理,處理方式同原型的處理方式。
第四,提供屬性裝飾器的拓展功能,Component只裝飾了類,如果想對(duì)類中的屬性做進(jìn)一步的處理,可以從此入手,比如vue-property-decorator庫(kù)提供的那些裝飾器就是依賴這個(gè)拓展功能。
說(shuō)到此,想必大家對(duì)前面的疑惑也釋然了,同時(shí)對(duì)vue-class-component的實(shí)現(xiàn)原理也有了一個(gè)大體的思路。因本人技術(shù)有限,文中可能存在膚淺、錯(cuò)誤的地方,如有發(fā)現(xiàn),還請(qǐng)不吝賜教,感謝!
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持創(chuàng)新互聯(lián)。
分享題目:深入理解vue-class-component源碼閱讀
分享網(wǎng)址:http://chinadenli.net/article6/jggpog.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供品牌網(wǎng)站制作、手機(jī)網(wǎng)站建設(shè)、響應(yīng)式網(wǎng)站、網(wǎng)站制作、網(wǎng)頁(yè)設(shè)計(jì)公司、品牌網(wǎng)站設(shè)計(jì)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)