JavaScript Reflect Metadata實現(xiàn)詳解
引言
在 ES6 的規(guī)范當中,就已經存在 Reflect API 了。簡單來說這個 API 的作用就是可以實現(xiàn)對變量操作的函數化,也就是反射。具體的關于這個 API 的內容,可以查看這個教程
然而我們在這里講到的,卻是 Reflect 里面還沒有的一個規(guī)范,那么就是 Reflect Metadata。
Metadata
想必對于其他語言的 Coder 來說,比如說 Java 或者 C#,Metadata 是很熟悉的。最簡單的莫過于通過反射來獲取類屬性上面的批注(在 JS 當中,也就是所謂的裝飾器)。從而可以更加優(yōu)雅的對代碼進行控制。
而 JS 現(xiàn)在有裝飾器,雖然現(xiàn)在還在 Stage2 階段。但是 JS 的裝飾器更多的是存在于對函數或者屬性進行一些操作,比如修改他們的值,代理變量,自動綁定 this 等等功能。
所以,后文當中我就使用 TypeScript 來進行講解,因為 TypeScript 已經完整的實現(xiàn)了裝飾器。
雖然 Babel 也可以,但是需要各種配置,人懶,不想配置那么多。
但是卻無法實現(xiàn)通過反射來獲取究竟有哪些裝飾器添加到這個類/方法上。
于是 Reflect Metadata 應運而生。
Reflect Metadata
Relfect Metadata,簡單來說,你可以通過裝飾器來給類添加一些自定義的信息。然后通過反射將這些信息提取出來。當然你也可以通過反射來添加這些信息。 就像是下面這個例子所示。
@Reflect.metadata('name', 'A') class A { @Reflect.metadata('hello', 'world') public hello(): string { return 'hello world' } } Reflect.getMetadata('name', A) // 'A' Reflect.getMetadata('hello', new A()) // 'world' // 這里為什么要用 new A(),用 A 不行么?后文會講到
是不是很簡單,那么我簡單來介紹一下~
概念
首先,在這里有四個概念要區(qū)分一下:
- Metadata Key {Any} 后文簡寫 k。元數據的 Key,對于一個對象來說,他可以有很多元數據,每一個元數據都對應有一個 Key。一個很簡單的例子就是說,你可以在一個對象上面設置一個叫做 'name' 的 Key 用來設置他的名字,用一個 'created time' 的 Key 來表示他創(chuàng)建的時間。這個 Key 可以是任意類型。在后面會講到內部本質就是一個 Map 對象。
- Metadata Value {Any} 后文簡寫 v。元數據的類型,任意類型都行。
- Target {Object} 后文簡寫 o。表示要在這個對象上面添加元數據
- Property {String|Symbol} 后文簡寫 p。用于設置在那個屬性上面添加元數據。大家可能會想,這個是干什么用的,不是可以在對象上面添加元數據了么?其實不僅僅可以在對象上面添加元數據,甚至還可以在對象的屬性上面添加元數據。其實大家可以這樣理解,當你給一個對象定義元數據的時候,相當于你是默認指定了 undefined 作為 Property。 下面有一個例子大家可以看一下。
大家明白了上面的概念之后,我之前給的那個例子就很簡單了~不用我多說了。
安裝/使用
下面不如正題,我們怎么開始使用 Reflect Metadata 呢?
首先,你需要安裝 reflect-metadata polyfill,然后引入之后就可以看到在 Reflect 對象下面有很多關于 Metadata 的函數了。因為這個還沒有進入正式的協(xié)議,所以需要安裝墊片使用。
啥,Reflect 是啥,一個全局變量而已。
你不需要擔心這個墊片的質量,因為連 Angular 都在使用呢,你怕啥。
之后你就可以安裝我上面寫的示例,在 TypeScript 當中去跑了。
類/屬性/方法 裝飾器
看這個例子。
@Reflect.metadata('name', 'A') class A { @Reflect.metadata('name', 'hello') hello() {} } const objs = [A, new A, A.prototype] const res = objs.map(obj => [ Reflect.getMetadata('name', obj), Reflect.getMetadata('name', obj, 'hello'), Reflect.getOwnMetadata('name', obj), Reflect.getOwnMetadata('name', obj ,'hello') ]) // 大家猜測一下 res 的值會是多少?
想好了么?再給你 10 秒鐘
10
9
8
7
6
5
4
3
2
1
res
[ ['A', undefined, 'A', undefined], [undefined, 'hello', undefined, undefined], [undefined, 'hello', undefined, 'hello'], ]
那么我來解釋一下為什么回是這樣的結果。
首先所有的對類的修飾,都是定義在類這個對象上面的,而所有的對類的屬性或者方法的修飾,都是定義在類的原型上面的,并且以屬性或者方法的 key 作為 property,這也就是為什么 getMetadata 會產生這樣的效果了。
那么帶 Own 的又是什么情況呢?
這就要從元數據的查找規(guī)則開始講起了
原型鏈查找
類似于類的繼承,查找元數據的方式也是通過原型鏈進行的。
就像是上面那個例子,我實例化了一個 new A(),但是我依舊可以找到他原型鏈上的元數據。
舉個例子
class A { @Reflect.metadata('name', 'hello') hello() {} } const t1 = new A() const t2 = new A() Reflect.defineMetadata('otherName', 'world', t2, 'hello') Reflect.getMetadata('name', t1, 'hello') // 'hello' Reflect.getMetadata('name', t2, 'hello') // 'hello' Reflect.getMetadata('otherName', t2, 'hello') // 'world' Reflect.getOwnMetadata('name', t2, 'hello') // undefined Reflect.getOwnMetadata('otherName', t2, 'hello') // 'world'
用途
其實所有的用途都是一個目的,給對象添加額外的信息,但是不影響對象的結構。這一點很重要,當你給對象添加了一個原信息的時候,對象是不會有任何的變化的,不會多 property,也不會有的 property 被修改了。
但是可以衍生出很多其他的用途。
- Anuglar 中對特殊字段進行修飾 (Input),從而提升代碼的可讀性。
- 可以讓裝飾器擁有真正裝飾對象而不改變對象的能力。讓對象擁有更多語義上的功能。
API
namespace Reflect { // 用于裝飾器 metadata(k, v): (target, property?) => void // 在對象上面定義元數據 defineMetadata(k, v, o, p?): void // 是否存在元數據 hasMetadata(k, o, p?): boolean hasOwnMetadata(k, o, p?): boolean // 獲取元數據 getMetadata(k, o, p?): any getOwnMetadata(k, o, p?): any // 獲取所有元數據的 Key getMetadataKeys(o, p?): any[] getOwnMetadataKeys(o, p?): any[] // 刪除元數據 deleteMetadata(k, o, p?): boolean }
大家可能注意到,針對某些操作,會有 Own 的函數。這是因為有的操作是可以通過原型鏈進行操作的。這個后文講解。
深入 Reflect Metadata
實現(xiàn)原理
如果你去翻看官網的文檔,他會和你說,所有的元數據都是存在于對象下面的 [[Metadata]] 屬性下面。一開始我也是這樣認為的,新建一個 Symbol('Metadata'),然后將元數據放到這個 Symbol 對應的 Property 當中。直到我看了源碼才發(fā)現(xiàn)并不是這樣。請看例子
@Reflect.metadata('name', 'A') class A {} Object.getOwnPropertySymbols(A) // []
哈哈,并沒有所謂的 Symbol,那么這些元數據都存在在哪里呢?
其實是內部的一個 WeakMap 中。他正是利用了 WeakMap 不增加引用計數的特點,將對象作為 Key,元數據集合作為 Value,存到 WeakMap 中去。
如果你認真探尋的話,你會發(fā)現(xiàn)其內部的數據結構其實是這樣的
WeakMap<any, Map<any, Map<any, any>>>
是不是超級繞,但是我們從調用的角度來思考,這就一點都不繞了。
weakMap.get(o).get(p).get(k)
先根據對象獲取,然后在根據屬性,最后根據元數據的 Key 獲取最終要的數據。
End
因為 Reflect Metadata 實在是比較簡單,這里就不多講解了。更多內容請查看Spec
題外話
其實看了源碼之后還是挺驚訝的,按照一般的套路,很多 polyfill 會讓你提供一些前置的 polyfill 之后,當前的 polyfill 才能使用。但是 reflect-metadata 竟然內部自己實現(xiàn)了很多的 polyfill 和算法。比如 Map, Set, WeakMap, UUID。最驚訝的莫過于 WeakMap 了。不是很仔細的閱讀了一下,好像還是會增加引用計數。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
textarea不能通過maxlength屬性來限制字數的解決方法
textarea稱文本域,又稱文本區(qū),其不能通過maxlength屬性來限制字數,為此必須尋求其他方法來加以限制以達到預設的需求2014-09-09基于BootStrap Metronic開發(fā)框架經驗小結【五】Bootstrap File Input文件上傳插件的用法
本文主要基于我自己的框架代碼案例,介紹其中文件上傳插件File Input的使用,非常具有參考借鑒價值,感興趣的朋友一起學習吧2016-05-05javaScript給元素添加多個class的簡單實現(xiàn)
下面小編就為大家?guī)硪黄猨avaScript給元素添加多個class的簡單實現(xiàn)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-07-07