這篇文章繼續(xù)Angular的學(xué)習(xí),帶大家了解一下Angular中的元資料和裝飾器,簡單了解一下他們的用法,希望對大家有所幫助!
作為「為大型前端專案」而設(shè)計的前端框架,Angular 其實有許多值得參考和學(xué)習(xí)的設(shè)計,本系列主要用於研究這些設(shè)計和功能的實現(xiàn)原理。本文主要圍繞在 Angular 中隨處可見的元數(shù)據(jù),來進行介紹。 【相關(guān)教學(xué)推薦:《angular教學(xué)》】
#裝飾器是使用 Angular 進行開發(fā)時的核心概念。在 Angular 中,裝飾器用於為類別或?qū)傩愿郊釉獢?shù)據(jù),來讓自己知道那些類別或?qū)傩缘暮x,以及該如何處理它們。
裝飾器與元資料
不管是裝飾器還是元數(shù)據(jù),都不是 Angular 提出的概念。因此,我們先來簡單了解一下。
元資料(Metadata)
在通用的概念中,元資料是描述使用者資料的資料。它總結(jié)了有關(guān)數(shù)據(jù)的基本信息,可以使查找和使用特定數(shù)據(jù)實例更加容易。例如,作者,建立日期,修改日期和文件大小是非?;镜奈臋n元資料的範例。
在用於類別的場景下,元資料用於裝飾類,來描述類別的定義和行為,以便可以配置類別的預(yù)期行為。
裝飾器(Decorator)
#裝飾器是JavaScript 的語言特性,是位於階段2(stage 2)的試驗特性。
裝飾器是定義期間在類,類元素或其他 JavaScript 語法形式上呼叫的函數(shù)。
裝飾器有三個主要功能:
可以用具有相同語意的匹配值取代正在修飾的值。 (例如,裝飾器可以將方法替換為另一種方法,將一個字段替換為另一個字段,將一個類替換為另一個類,等等)。
可以將元資料與正在修飾的值相關(guān)聯(lián);可以從外部讀取此元數(shù)據(jù),並將其用於元程式設(shè)計和自我檢查。
可以透過元資料提供對正在修飾的值的存取。對於公共值,他們可以透過值名稱來實現(xiàn);對於私有值,它們接收存取器函數(shù),然後可以選擇共用它們。
本質(zhì)上,裝飾器可用於對值進行元編程和向其添加功能,而無需從根本上改變其外部行為。
更多的內(nèi)容,可以參考 tc39/proposal-decorators 提案。
Angular 中的裝飾器和元資料
我們在開發(fā) Angular 應(yīng)用程式時,不管是元件、指令,或是服務(wù)、模組等,都需要透過裝飾器來進行定義和開發(fā)。裝飾器會出現(xiàn)在類別定義的緊前方,用來聲明該類別具有指定的類型,並且提供適合該類型的元資料。
例如,我們可以用下列裝飾器來宣告Angular 的類別:@Component()
、@Directive()
、@Pipe()
、@Injectable()
、@NgModule()
。
使用裝飾器和元資料來改變類別的行為
#以@Component()
為例,該裝飾器的作用包括:
將類別標記為Angular 元件。
提供可配置的元數(shù)據(jù),用來決定在執(zhí)行時應(yīng)如何處理、實例化和使用該元件。
關(guān)於@Component()
該如何使用可以參考,這裡不多介紹。讓我們來看看這個裝飾器的定義:
// 提供 Angular 組件的配置元數(shù)據(jù)接口定義 // Angular 中,組件是指令的子集,始終與模板相關(guān)聯(lián) export interface Component extends Directive { // changeDetection 用于此組件的變更檢測策略 // 實例化組件時,Angular 將創(chuàng)建一個更改檢測器,該更改檢測器負責(zé)傳播組件的綁定。 changeDetection?: ChangeDetectionStrategy; // 定義對其視圖 DOM 子對象可見的可注入對象的集合 viewProviders?: Provider[]; // 包含組件的模塊的模塊ID,該組件必須能夠解析模板和樣式的相對 URL moduleId?: string; ... // 模板和 CSS 樣式的封裝策略 encapsulation?: ViewEncapsulation; // 覆蓋默認的插值起始和終止定界符(`{{`和`}}`) interpolation?: [string, string]; } // 組件裝飾器和元數(shù)據(jù) export const Component: ComponentDecorator = makeDecorator( 'Component', // 使用默認的 CheckAlways 策略,在該策略中,更改檢測是自動進行的,直到明確停用為止。 (c: Component = {}) => ({changeDetection: ChangeDetectionStrategy.Default, ...c}), Directive, undefined, (type: Type<any>, meta: Component) => SWITCH_COMPILE_COMPONENT(type, meta));
以上便是元件裝飾、元件元資料的定義,我們來看看裝飾器的創(chuàng)建過程。
裝飾器的建立過程
我們可以從原始碼中找到,元件和指令的裝飾器都會透過makeDecorator()
來產(chǎn)生:
export function makeDecorator<T>( name: string, props?: (...args: any[]) => any, parentClass?: any, // 裝飾器名字和屬性 additionalProcessing?: (type: Type<T>) => void, typeFn?: (type: Type<T>, ...args: any[]) => void): {new (...args: any[]): any; (...args: any[]): any; (...args: any[]): (cls: any) => any;} { // noSideEffects 用于確認閉包編譯器包裝的函數(shù)沒有副作用 return noSideEffects(() => { const metaCtor = makeMetadataCtor(props); // 裝飾器工廠 function DecoratorFactory( this: unknown|typeof DecoratorFactory, ...args: any[]): (cls: Type<T>) => 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<T>) { // 編譯類 if (typeFn) typeFn(cls, ...args); // 使用 Object.defineProperty 很重要,因為它會創(chuàng)建不可枚舉的屬性,從而防止該屬性在子類化過程中被復(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) { // 繼承父類 DecoratorFactory.prototype = Object.create(parentClass.prototype); } DecoratorFactory.prototype.ngMetadataName = name; (DecoratorFactory as any).annotationCls = DecoratorFactory; return DecoratorFactory as any; }); }
在上面的範例中,我們透過makeDecorator()
產(chǎn)生了一個用於定義元件的Component
裝飾器工廠。當(dāng)使用@Component()
建立元件時,Angular 會根據(jù)元資料來編譯元件。
根據(jù)裝飾器元資料編譯元件
Angular 會根據(jù)該裝飾器元數(shù)據(jù),來編譯Angular 元件,然後將產(chǎn)生的元件定義(?cmp
)修補到元件類型上:
export function compileComponent(type: Type<any>, metadata: Component): void { // 初始化 ngDevMode (typeof ngDevMode === 'undefined' || ngDevMode) && initNgDevMode(); let ngComponentDef: any = null; // 元數(shù)據(jù)可能具有需要解析的資源 maybeQueueResolutionOfComponentResources(type, metadata); // 這里使用的功能與指令相同,因為這只是創(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, }; // 編譯過程需要計算深度,以便確認編譯是否最終完成 compilationDepth++; try { if (meta.usesInheritance) { addDirectiveDefToUndecoratedParents(type); } // 根據(jù)模板、環(huán)境和組件需要的元數(shù)據(jù),來編譯組件 ngComponentDef = compiler.compileComponent(angularCoreEnv, templateUrl, meta); } finally { // 即使編譯失敗,也請確保減少編譯深度 compilationDepth--; } if (compilationDepth === 0) { // 當(dāng)執(zhí)行 NgModule 裝飾器時,我們將模塊定義加入隊列,以便僅在所有聲明都已解析的情況下才將隊列出隊,并將其自身作為模塊作用域添加到其所有聲明中 // 此調(diào)用運行檢查以查看隊列中的任何模塊是否可以出隊,并將范圍添加到它們的聲明中 flushModuleScopingQueueAsMuchAsPossible(); } // 如果組件編譯是異步的,則聲明該組件的 @NgModule 批注可以執(zhí)行并在組件類型上設(shè)置 ngSelectorScope 屬性 // 這允許組件在完成編譯后,使用模塊中的 directiveDefs 對其自身進行修補 if (hasSelectorScope(type)) { const scopes = transitiveScopesFor(type.ngSelectorScope); patchComponentDefWithScope(ngComponentDef, scopes); } } return ngComponentDef; }, ... }); }
編譯組件的過程可能是異步的(比如需要解析組件模板或其他資源的 URL)。如果編譯不是立即進行的,compileComponent
會將資源解析加入到全局隊列中,并且將無法返回?cmp
,直到通過調(diào)用resolveComponentResources
解決了全局隊列為止。
編譯過程中的元數(shù)據(jù)
元數(shù)據(jù)是有關(guān)類的信息,但它不是類的屬性。因此,用于配置類的定義和行為的這些數(shù)據(jù),不應(yīng)該存儲在該類的實例中,我們還需要在其他地方保存此數(shù)據(jù)。
在 Angular 中,編譯過程產(chǎn)生的元數(shù)據(jù),會使用CompileMetadataResolver
來進行管理和維護,這里我們主要看指令(組件)相關(guān)的邏輯:
export class CompileMetadataResolver { private _nonNormalizedDirectiveCache = new Map<Type, {annotation: Directive, metadata: cpl.CompileDirectiveMetadata}>(); // 使用 Map 的方式來保存 private _directiveCache = new Map<Type, cpl.CompileDirectiveMetadata>(); private _summaryCache = new Map<Type, cpl.CompileTypeSummary|null>(); private _pipeCache = new Map<Type, cpl.CompilePipeMetadata>(); private _ngModuleCache = new Map<Type, cpl.CompileNgModuleMetadata>(); private _ngModuleOfTypes = new Map<Type, Type>(); private _shallowModuleCache = new Map<Type, cpl.CompileShallowModuleMetadata>(); constructor( private _config: CompilerConfig, private _htmlParser: HtmlParser, private _ngModuleResolver: NgModuleResolver, private _directiveResolver: DirectiveResolver, private _pipeResolver: PipeResolver, private _summaryResolver: SummaryResolver<any>, private _schemaRegistry: ElementSchemaRegistry, private _directiveNormalizer: DirectiveNormalizer, private _console: Console, private _staticSymbolCache: StaticSymbolCache, private _reflector: CompileReflector, private _errorCollector?: ErrorCollector) {} // 清除特定某個指令的元數(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<any> { const ngModule = this.getNgModuleMetadata(moduleType, throwIfNotFound); const loading: Promise<any>[] = []; 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<null> { // 若已加載,則直接返回 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); } // 存儲完整的元數(shù)據(jù)信息,以及元數(shù)據(jù)摘要信息 this._directiveCache.set(directiveType, normalizedDirMeta); this._summaryCache.set(directiveType, normalizedDirMeta.toSummary()); return null; }; if (metadata.isComponent) { // 如果是組件,該過程可能為異步過程,則需要等待異步過程結(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ù)進行存儲 return SyncAsync.then(templateMeta, createDirectiveMetadata); } else { // 指令,直接存儲元數(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 = <cpl.CompileDirectiveSummary>this._loadSummary(dirType, cpl.CompileSummaryKind.Directive); ... return dirSummary; } }
可以看到,在編譯過程中,不管是組件、指令、管道,還是模塊,這些類在編譯過程中的元數(shù)據(jù),都使用Map
來存儲。
總結(jié)
本節(jié)我們介紹了 Angular 中的裝飾器和元數(shù)據(jù),其中元數(shù)據(jù)用于描述類的定義和行為。
在 Angular 編譯過程中,會使用Map
的數(shù)據(jù)結(jié)構(gòu)來維護和存儲裝飾器的元數(shù)據(jù),并根據(jù)這些元數(shù)據(jù)信息來編譯組件、指令、管道和模塊等。
更多編程相關(guān)知識,請訪問:編程教學(xué)?。?/p>
以上是聊聊Angular中的元資料(Metadata)和裝飾器(Decorator)的詳細內(nèi)容。更多資訊請關(guān)注PHP中文網(wǎng)其他相關(guān)文章!

熱AI工具

Undress AI Tool
免費脫衣圖片

Undresser.AI Undress
人工智慧驅(qū)動的應(yīng)用程序,用於創(chuàng)建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發(fā)環(huán)境

Dreamweaver CS6
視覺化網(wǎng)頁開發(fā)工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

簡介上週,我寫了一篇關(guān)於抓取網(wǎng)頁以收集元資料的介紹,並提到不可能抓取《紐約時報》網(wǎng)站。 《紐約時報》付費牆會阻止您收集基本元資料的嘗試。但有一種方法可以使用紐約時報API來解決這個問題。最近我開始在Yii平臺上建立一個社群網(wǎng)站,我將在以後的教程中發(fā)布該網(wǎng)站。我希望能夠輕鬆添加與網(wǎng)站內(nèi)容相關(guān)的連結(jié)。雖然人們可以輕鬆地將URL貼到表單中,但提供標題和來源資訊卻非常耗時。因此,在今天的教程中,我將擴展我最近編寫的抓取程式碼,以在添加《紐約時報》連結(jié)時利用《紐約時報》API來收集頭條新聞。請記住,我參與了

Angular.js是一種可自由存取的JavaScript平臺,用於建立動態(tài)應(yīng)用程式。它允許您透過擴展HTML的語法作為模板語言,以快速、清晰地表示應(yīng)用程式的各個方面。 Angular.js提供了一系列工具,可協(xié)助您編寫、更新和測試程式碼。此外,它還提供了許多功能,如路由和表單管理。本指南將討論在Ubuntu24上安裝Angular的方法。首先,您需要安裝Node.js。 Node.js是一個基於ChromeV8引擎的JavaScript運行環(huán)境,可讓您在伺服器端執(zhí)行JavaScript程式碼。要在Ub

angular中怎麼使用monaco-editor?以下這篇文章記錄下最近的一次業(yè)務(wù)中用到的 m??onaco-editor 在 angular 中的使用,希望對大家有幫助!

隨著網(wǎng)路的快速發(fā)展,前端開發(fā)技術(shù)也不斷改進與迭代。 PHP和Angular是兩種廣泛應(yīng)用於前端開發(fā)的技術(shù)。 PHP是一種伺服器端腳本語言,可以處理表單、產(chǎn)生動態(tài)頁面和管理存取權(quán)限等任務(wù)。而Angular是一種JavaScript的框架,可以用來開發(fā)單一頁面應(yīng)用程式和建構(gòu)元件化的網(wǎng)頁應(yīng)用程式。本篇文章將介紹如何使用PHP和Angular進行前端開發(fā),以及如何將它們

你知道 Angular Universal 嗎?可以幫助網(wǎng)站提供更好的 SEO 支援哦!

我們可以使用Mutagen和Python中的eyeD3模組存取音訊檔案的元資料。對於視訊元數(shù)據(jù),我們可以使用影片和Python中的OpenCV庫。元資料是提供有關(guān)其他資料(例如音訊和視訊資料)的資訊的資料。音訊和視訊檔案的元資料包括檔案格式、檔案解析度、檔案大小、持續(xù)時間、位元率等。透過存取這些元數(shù)據(jù),我們可以更有效地管理媒體並分析元數(shù)據(jù)以獲得一些有用的信息。在本文中,我們將了解Python提供的一些用於存取音訊和視訊檔案元資料的庫或模組。存取音頻元資料一些用於存取音訊檔案元資料的庫是-使用誘變

身份驗證是任何網(wǎng)路應(yīng)用程式中最重要的部分之一。本教程討論基於令牌的身份驗證系統(tǒng)以及它們與傳統(tǒng)登入系統(tǒng)的差異。在本教程結(jié)束時,您將看到一個用Angular和Node.js編寫的完整工作演示。傳統(tǒng)身份驗證系統(tǒng)在繼續(xù)基於令牌的身份驗證系統(tǒng)之前,讓我們先來看看傳統(tǒng)的身份驗證系統(tǒng)。使用者在登入表單中提供使用者名稱和密碼,然後點擊登入。發(fā)出請求後,透過查詢資料庫在後端驗證使用者。如果請求有效,則使用從資料庫中獲取的使用者資訊建立會話,然後在回應(yīng)頭中傳回會話訊息,以便將會話ID儲存在瀏覽器中。提供用於存取應(yīng)用程式中受

Angular框架中元件的預(yù)設(shè)顯示行為不是區(qū)塊級元素。這種設(shè)計選擇促進了元件樣式的封裝,並鼓勵開發(fā)人員有意識地定義每個元件的顯示方式。透過明確設(shè)定CSS屬性 display,Angular組件的顯示可以完全控制,從而實現(xiàn)所需的佈局和響應(yīng)能力。
