本篇內(nèi)容主要講解“Angular中的元數(shù)據(jù)和裝飾器怎么應(yīng)用”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“Angular中的元數(shù)據(jù)和裝飾器怎么應(yīng)用”吧!
專(zhuān)注于為中小企業(yè)提供成都做網(wǎng)站、網(wǎng)站制作服務(wù),電腦端+手機(jī)端+微信端的三站合一,更高效的管理,為中小企業(yè)威信免費(fèi)做網(wǎng)站提供優(yōu)質(zhì)的服務(wù)。我們立足成都,凝聚了一批互聯(lián)網(wǎng)行業(yè)人才,有力地推動(dòng)了成百上千家企業(yè)的穩(wěn)健成長(zhǎng),幫助中小企業(yè)通過(guò)網(wǎng)站建設(shè)實(shí)現(xiàn)規(guī)模擴(kuò)充和轉(zhuǎn)變。
作為“為大型前端項(xiàng)目”而設(shè)計(jì)的前端框架,Angular 其實(shí)有許多值得參考和學(xué)習(xí)的設(shè)計(jì),本系列主要用于研究這些設(shè)計(jì)和功能的實(shí)現(xiàn)原理。本文主要圍繞 Angular 中隨處可見(jiàn)的元數(shù)據(jù),來(lái)進(jìn)行介紹。
裝飾器是使用 Angular 進(jìn)行開(kāi)發(fā)時(shí)的核心概念。在 Angular 中,裝飾器用于為類(lèi)或?qū)傩愿郊釉獢?shù)據(jù),來(lái)讓自己知道那些類(lèi)或?qū)傩缘暮x,以及該如何處理它們。
不管是裝飾器還是元數(shù)據(jù),都不是由 Angular 提出的概念。因此,我們先來(lái)簡(jiǎn)單了解一下。
元數(shù)據(jù)(Metadata)
在通用的概念中,元數(shù)據(jù)是描述用戶(hù)數(shù)據(jù)的數(shù)據(jù)。它總結(jié)了有關(guān)數(shù)據(jù)的基本信息,可以使查找和使用特定數(shù)據(jù)實(shí)例更加容易。例如,作者,創(chuàng)建日期,修改日期和文件大小是非常基本的文檔元數(shù)據(jù)的示例。
在用于類(lèi)的場(chǎng)景下,元數(shù)據(jù)用于裝飾類(lèi),來(lái)描述類(lèi)的定義和行為,以便可以配置類(lèi)的預(yù)期行為。
裝飾器(Decorator)
裝飾器是 JavaScript 的一種語(yǔ)言特性,是一項(xiàng)位于階段 2(stage 2)的試驗(yàn)特性。
裝飾器是定義期間在類(lèi),類(lèi)元素或其他 JavaScript 語(yǔ)法形式上調(diào)用的函數(shù)。
裝飾器具有三個(gè)主要功能:
可以用具有相同語(yǔ)義的匹配值替換正在修飾的值。(例如,裝飾器可以將方法替換為另一種方法,將一個(gè)字段替換為另一個(gè)字段,將一個(gè)類(lèi)替換為另一個(gè)類(lèi),等等)。
可以將元數(shù)據(jù)與正在修飾的值相關(guān)聯(lián);可以從外部讀取此元數(shù)據(jù),并將其用于元編程和自我檢查。
可以通過(guò)元數(shù)據(jù)提供對(duì)正在修飾的值的訪問(wèn)。對(duì)于公共值,他們可以通過(guò)值名稱(chēng)來(lái)實(shí)現(xiàn);對(duì)于私有值,它們接收訪問(wèn)器函數(shù),然后可以選擇共享它們。
本質(zhì)上,裝飾器可用于對(duì)值進(jìn)行元編程和向其添加功能,而無(wú)需從根本上改變其外部行為。
更多的內(nèi)容,可以參考 tc39/proposal-decorators 提案。
我們?cè)陂_(kāi)發(fā) Angular 應(yīng)用時(shí),不管是組件、指令,還是服務(wù)、模塊等,都需要通過(guò)裝飾器來(lái)進(jìn)行定義和開(kāi)發(fā)。裝飾器會(huì)出現(xiàn)在類(lèi)定義的緊前方,用來(lái)聲明該類(lèi)具有指定的類(lèi)型,并且提供適合該類(lèi)型的元數(shù)據(jù)。
比如,我們可以用下列裝飾器來(lái)聲明 Angular 的類(lèi):@Component()
、@Directive()
、@Pipe()
、@Injectable()
、@NgModule()
。
使用裝飾器和元數(shù)據(jù)來(lái)改變類(lèi)的行為
以@Component()
為例,該裝飾器的作用包括:
將類(lèi)標(biāo)記為 Angular 組件。
提供可配置的元數(shù)據(jù),用來(lái)確定應(yīng)在運(yùn)行時(shí)如何處理、實(shí)例化和使用該組件。
關(guān)于@Component()
該如何使用可以參考,這里不多介紹。我們來(lái)看看這個(gè)裝飾器的定義:
// 提供 Angular 組件的配置元數(shù)據(jù)接口定義 // Angular 中,組件是指令的子集,始終與模板相關(guān)聯(lián) export interface Component extends Directive { // changeDetection 用于此組件的變更檢測(cè)策略 // 實(shí)例化組件時(shí),Angular 將創(chuàng)建一個(gè)更改檢測(cè)器,該更改檢測(cè)器負(fù)責(zé)傳播組件的綁定。 changeDetection?: ChangeDetectionStrategy; // 定義對(duì)其視圖 DOM 子對(duì)象可見(jiàn)的可注入對(duì)象的集合 viewProviders?: Provider[]; // 包含組件的模塊的模塊ID,該組件必須能夠解析模板和樣式的相對(duì) URL moduleId?: string; ... // 模板和 CSS 樣式的封裝策略 encapsulation?: ViewEncapsulation; // 覆蓋默認(rèn)的插值起始和終止定界符(`{{`和`}}`) interpolation?: [string, string]; } // 組件裝飾器和元數(shù)據(jù) export const Component: ComponentDecorator = makeDecorator( 'Component', // 使用默認(rèn)的 CheckAlways 策略,在該策略中,更改檢測(cè)是自動(dòng)進(jìn)行的,直到明確停用為止。 (c: Component = {}) => ({changeDetection: ChangeDetectionStrategy.Default, ...c}), Directive, undefined, (type: Type, meta: Component) => SWITCH_COMPILE_COMPONENT(type, meta));
以上便是組件裝飾、組件元數(shù)據(jù)的定義,我們來(lái)看看裝飾器的創(chuàng)建過(guò)程。
裝飾器的創(chuàng)建過(guò)程
我們可以從源碼中找到,組件和指令的裝飾器都會(huì)通過(guò)makeDecorator()
來(lái)產(chǎn)生:
export function makeDecorator( name: string, props?: (...args: any[]) => any, parentClass?: any, // 裝飾器名字和屬性 additionalProcessing?: (type: Type ) => void, typeFn?: (type: Type , ...args: any[]) => void): {new (...args: any[]): any; (...args: any[]): any; (...args: any[]): (cls: any) => any;} { // noSideEffects 用于確認(rèn)閉包編譯器包裝的函數(shù)沒(méi)有副作用 return noSideEffects(() => { const metaCtor = makeMetadataCtor(props); // 裝飾器工廠 function DecoratorFactory( this: unknown|typeof DecoratorFactory, ...args: any[]): (cls: Type ) => any { if (this instanceof DecoratorFactory) { // 賦值元數(shù)據(jù) metaCtor.call(this, ...args); return this as typeof DecoratorFactory; } // 創(chuàng)建裝飾器工廠 const annotationInstance = new (DecoratorFactory as any)(...args); return function TypeDecorator(cls: Type ) { // 編譯類(lèi) if (typeFn) typeFn(cls, ...args); // 使用 Object.defineProperty 很重要,因?yàn)樗鼤?huì)創(chuàng)建不可枚舉的屬性,從而防止該屬性在子類(lèi)化過(guò)程中被復(fù)制。 const annotations = cls.hasOwnProperty(ANNOTATIONS) ? (cls as any)[ANNOTATIONS] : Object.defineProperty(cls, ANNOTATIONS, {value: []})[ANNOTATIONS]; annotations.push(annotationInstance); // 特定邏輯的執(zhí)行 if (additionalProcessing) additionalProcessing(cls); return cls; }; } if (parentClass) { // 繼承父類(lèi) DecoratorFactory.prototype = Object.create(parentClass.prototype); } DecoratorFactory.prototype.ngMetadataName = name; (DecoratorFactory as any).annotationCls = DecoratorFactory; return DecoratorFactory as any; }); }
在上面的例子中,我們通過(guò)makeDecorator()
產(chǎn)生了一個(gè)用于定義組件的Component
裝飾器工廠。當(dāng)使用@Component()
創(chuàng)建組件時(shí),Angular 會(huì)根據(jù)元數(shù)據(jù)來(lái)編譯組件。
根據(jù)裝飾器元數(shù)據(jù)編譯組件
Angular 會(huì)根據(jù)該裝飾器元數(shù)據(jù),來(lái)編譯 Angular 組件,然后將生成的組件定義(?cmp
)修補(bǔ)到組件類(lèi)型上:
export function compileComponent(type: Type, metadata: Component): void { // 初始化 ngDevMode (typeof ngDevMode === 'undefined' || ngDevMode) && initNgDevMode(); let ngComponentDef: any = null; // 元數(shù)據(jù)可能具有需要解析的資源 maybeQueueResolutionOfComponentResources(type, metadata); // 這里使用的功能與指令相同,因?yàn)檫@只是創(chuàng)建 ngFactoryDef 所需的元數(shù)據(jù)的子集 addDirectiveFactoryDef(type, metadata); Object.defineProperty(type, NG_COMP_DEF, { get: () => { if (ngComponentDef === null) { const compiler = getCompilerFacade(); // 根據(jù)元數(shù)據(jù)解析組件 if (componentNeedsResolution(metadata)) { ... // 異常處理 } ... // 創(chuàng)建編譯組件需要的完整元數(shù)據(jù) const templateUrl = metadata.templateUrl || `ng:///${type.name}/template.html`; const meta: R3ComponentMetadataFacade = { ...directiveMetadata(type, metadata), typeSourceSpan: compiler.createParseSourceSpan('Component', type.name, templateUrl), template: metadata.template || '', preserveWhitespaces, styles: metadata.styles || EMPTY_ARRAY, animations: metadata.animations, directives: [], changeDetection: metadata.changeDetection, pipes: new Map(), encapsulation, interpolation: metadata.interpolation, viewProviders: metadata.viewProviders || null, }; // 編譯過(guò)程需要計(jì)算深度,以便確認(rèn)編譯是否最終完成 compilationDepth++; try { if (meta.usesInheritance) { addDirectiveDefToUndecoratedParents(type); } // 根據(jù)模板、環(huán)境和組件需要的元數(shù)據(jù),來(lái)編譯組件 ngComponentDef = compiler.compileComponent(angularCoreEnv, templateUrl, meta); } finally { // 即使編譯失敗,也請(qǐng)確保減少編譯深度 compilationDepth--; } if (compilationDepth === 0) { // 當(dāng)執(zhí)行 NgModule 裝飾器時(shí),我們將模塊定義加入隊(duì)列,以便僅在所有聲明都已解析的情況下才將隊(duì)列出隊(duì),并將其自身作為模塊作用域添加到其所有聲明中 // 此調(diào)用運(yùn)行檢查以查看隊(duì)列中的任何模塊是否可以出隊(duì),并將范圍添加到它們的聲明中 flushModuleScopingQueueAsMuchAsPossible(); } // 如果組件編譯是異步的,則聲明該組件的 @NgModule 批注可以執(zhí)行并在組件類(lèi)型上設(shè)置 ngSelectorScope 屬性 // 這允許組件在完成編譯后,使用模塊中的 directiveDefs 對(duì)其自身進(jìn)行修補(bǔ) if (hasSelectorScope(type)) { const scopes = transitiveScopesFor(type.ngSelectorScope); patchComponentDefWithScope(ngComponentDef, scopes); } } return ngComponentDef; }, ... }); }
編譯組件的過(guò)程可能是異步的(比如需要解析組件模板或其他資源的 URL)。如果編譯不是立即進(jìn)行的,compileComponent
會(huì)將資源解析加入到全局隊(duì)列中,并且將無(wú)法返回?cmp
,直到通過(guò)調(diào)用resolveComponentResources
解決了全局隊(duì)列為止。
編譯過(guò)程中的元數(shù)據(jù)
元數(shù)據(jù)是有關(guān)類(lèi)的信息,但它不是類(lèi)的屬性。因此,用于配置類(lèi)的定義和行為的這些數(shù)據(jù),不應(yīng)該存儲(chǔ)在該類(lèi)的實(shí)例中,我們還需要在其他地方保存此數(shù)據(jù)。
在 Angular 中,編譯過(guò)程產(chǎn)生的元數(shù)據(jù),會(huì)使用CompileMetadataResolver
來(lái)進(jìn)行管理和維護(hù),這里我們主要看指令(組件)相關(guān)的邏輯:
export class CompileMetadataResolver { private _nonNormalizedDirectiveCache = new Map(); // 使用 Map 的方式來(lái)保存 private _directiveCache = new Map (); private _summaryCache = new Map (); private _pipeCache = new Map (); private _ngModuleCache = new Map (); private _ngModuleOfTypes = new Map (); private _shallowModuleCache = new Map (); constructor( private _config: CompilerConfig, private _htmlParser: HtmlParser, private _ngModuleResolver: NgModuleResolver, private _directiveResolver: DirectiveResolver, private _pipeResolver: PipeResolver, private _summaryResolver: SummaryResolver , private _schemaRegistry: ElementSchemaRegistry, private _directiveNormalizer: DirectiveNormalizer, private _console: Console, private _staticSymbolCache: StaticSymbolCache, private _reflector: CompileReflector, private _errorCollector?: ErrorCollector) {} // 清除特定某個(gè)指令的元數(shù)據(jù) clearCacheFor(type: Type) { const dirMeta = this._directiveCache.get(type); this._directiveCache.delete(type); ... } // 清除所有元數(shù)據(jù) clearCache(): void { this._directiveCache.clear(); ... } /** * 加載 NgModule 中,已聲明的指令和的管道 */ loadNgModuleDirectiveAndPipeMetadata(moduleType: any, isSync: boolean, throwIfNotFound = true): Promise { const ngModule = this.getNgModuleMetadata(moduleType, throwIfNotFound); const loading: Promise [] = []; if (ngModule) { ngModule.declaredDirectives.forEach((id) => { const promise = this.loadDirectiveMetadata(moduleType, id.reference, isSync); if (promise) { loading.push(promise); } }); ngModule.declaredPipes.forEach((id) => this._loadPipeMetadata(id.reference)); } return Promise.all(loading); } // 加載指令(組件)元數(shù)據(jù) loadDirectiveMetadata(ngModuleType: any, directiveType: any, isSync: boolean): SyncAsync { // 若已加載,則直接返回 if (this._directiveCache.has(directiveType)) { return null; } directiveType = resolveForwardRef(directiveType); const {annotation, metadata} = this.getNonNormalizedDirectiveMetadata(directiveType)!; // 創(chuàng)建指令(組件)元數(shù)據(jù) const createDirectiveMetadata = (templateMetadata: cpl.CompileTemplateMetadata|null) => { const normalizedDirMeta = new cpl.CompileDirectiveMetadata({ isHost: false, type: metadata.type, isComponent: metadata.isComponent, selector: metadata.selector, exportAs: metadata.exportAs, changeDetection: metadata.changeDetection, inputs: metadata.inputs, outputs: metadata.outputs, hostListeners: metadata.hostListeners, hostProperties: metadata.hostProperties, hostAttributes: metadata.hostAttributes, providers: metadata.providers, viewProviders: metadata.viewProviders, queries: metadata.queries, guards: metadata.guards, viewQueries: metadata.viewQueries, entryComponents: metadata.entryComponents, componentViewType: metadata.componentViewType, rendererType: metadata.rendererType, componentFactory: metadata.componentFactory, template: templateMetadata }); if (templateMetadata) { this.initComponentFactory(metadata.componentFactory!, templateMetadata.ngContentSelectors); } // 存儲(chǔ)完整的元數(shù)據(jù)信息,以及元數(shù)據(jù)摘要信息 this._directiveCache.set(directiveType, normalizedDirMeta); this._summaryCache.set(directiveType, normalizedDirMeta.toSummary()); return null; }; if (metadata.isComponent) { // 如果是組件,該過(guò)程可能為異步過(guò)程,則需要等待異步過(guò)程結(jié)束后的模板返回 const template = metadata.template !; const templateMeta = this._directiveNormalizer.normalizeTemplate({ ngModuleType, componentType: directiveType, moduleUrl: this._reflector.componentModuleUrl(directiveType, annotation), encapsulation: template.encapsulation, template: template.template, templateUrl: template.templateUrl, styles: template.styles, styleUrls: template.styleUrls, animations: template.animations, interpolation: template.interpolation, preserveWhitespaces: template.preserveWhitespaces }); if (isPromise(templateMeta) && isSync) { this._reportError(componentStillLoadingError(directiveType), directiveType); return null; } // 并將元數(shù)據(jù)進(jìn)行存儲(chǔ) return SyncAsync.then(templateMeta, createDirectiveMetadata); } else { // 指令,直接存儲(chǔ)元數(shù)據(jù) createDirectiveMetadata(null); return null; } } // 獲取給定指令(組件)的元數(shù)據(jù)信息 getDirectiveMetadata(directiveType: any): cpl.CompileDirectiveMetadata { const dirMeta = this._directiveCache.get(directiveType)!; ... return dirMeta; } // 獲取給定指令(組件)的元數(shù)據(jù)摘要信息 getDirectiveSummary(dirType: any): cpl.CompileDirectiveSummary { const dirSummary = this._loadSummary(dirType, cpl.CompileSummaryKind.Directive); ... return dirSummary; } }
可以看到,在編譯過(guò)程中,不管是組件、指令、管道,還是模塊,這些類(lèi)在編譯過(guò)程中的元數(shù)據(jù),都使用Map
來(lái)存儲(chǔ)。
到此,相信大家對(duì)“Angular中的元數(shù)據(jù)和裝飾器怎么應(yīng)用”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢(xún),關(guān)注我們,繼續(xù)學(xué)習(xí)!