直接看人話總結html
angular 源碼閱讀vue
項目地址react
該文章項目地址git
angular 版本:8.0.0-rc.4github
歡迎看看個人類angular框架typescript
試讀angular源碼第一章:開場與platformBrowserDynamicbootstrap
試讀angular源碼第二章:引導模塊bootstrapModule後端
試讀angular源碼第四章:angular模塊及JIT編譯模塊瀏覽器
聲明:僅僅爲我的閱讀源碼的理解,不必定徹底正確,還須要大佬的指點。
其實市面上不少關於 vue和react 的源碼閱讀,可是基本上沒有看到關於 angular 系統性地源碼閱讀。
並且大部分人一據說 angular 就會本能地避開。
但其實不是的,在我眼裏 angular 只是套用了不少後端已有的概念,好比 DI,好比 AOT 等。
以前我寫過一個類 angular 的框架 InDiv,基本上實現了大多數 ng 的裝飾器。
並且在寫這個項目的時候,我從 angular 上學到了不少。
此次,則但願經過閱讀 angular 的源代碼,學習到更多谷歌在設計模式上的運用,學習到更多代碼優化和結構的運用。
也有一點私心,但願更多人說 ng大法好 ,哈哈。
但願看以前讀者能先了解一下 typescripy 和 angular 的基礎概念,由於文章裏會出現大量的 DI,服務商啊這類詞
項目下只有三個文件夾:angular docs 和 my-demo
- angular: 註釋版angular的ts源代碼
- docs: 文檔位置
- my-demo: 啓動的一個demo項目
複製代碼
經過 tsconfig
把 angular 別名設置到 angular這個文件夾,來閱讀下 ts 版本的源碼。
在瀏覽器端,每一個 angular app都是從 main.ts
開始的。
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));
複製代碼
至於啓動項目,都是這一行 platformBrowserDynamic().bootstrapModule(AppModule)
開始的。
在 angular 的世界中,全部的app都是由 platformBrowserDynamic()
提供的 bootstrapModule
方法引導根模塊或主模塊啓動的。
angular 抽象出 platform,來實現跨平臺。
實例化 angular 根模塊的 bootstrapModule
的方法在瀏覽器端來自 @angular/platform-browser-dynamic
。
其實除了 @angular/platform-browser-dynamic
以外還有 @angular/platform-browser
。
這兩個模塊的主要區別是編譯方式的不一樣, platform-browser-dynamic
提供 JIT 編譯,也就是說編譯在瀏覽器內完成,而 platform-browser
提供 AOT 編譯,編譯在本地完成。
angular/packages/platform-browser-dynamic/src/platform-browser-dynamic.ts
/** * @publicApi */
export const platformBrowserDynamic = createPlatformFactory(
platformCoreDynamic, 'browserDynamic', INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS);
複製代碼
platformBrowserDynamic
方法很簡單,就是調用建立平臺的工廠方法 createPlatformFactory
返回的一個返回值是平臺實例 PlatformRef
的函數。
angular/packages/core/src/application_ref.ts
/**
* Creates a factory for a platform
*
* 1. 判斷是否已經建立過了
* 2. 判斷是否有父 `Factory`
* 3. 若是有父 `Factory` 就把調用 `Factory` 時傳入的 `Provider` 和調用 `createPlatformFactory` 傳入的 `Provider` 合併,而後調用父 `Factory`
* 4. 若是沒有父 `Factory` ,先建立一個 `Injector` ,而後去建立 `PlatformRef` 實例
*
* @publicApi
*/
export function createPlatformFactory(
parentPlatformFactory: ((extraProviders?: StaticProvider[]) => PlatformRef) | null,
name: string, providers: StaticProvider[] = []): (extraProviders?: StaticProvider[]) =>
PlatformRef {
const desc = `Platform: ${name}`;
const marker = new InjectionToken(desc);
return (extraProviders: StaticProvider[] = []) => {
let platform = getPlatform();
// 註釋:判斷是否存在平臺實例
if (!platform || platform.injector.get(ALLOW_MULTIPLE_PLATFORMS, false)) {
if (parentPlatformFactory) {
// 註釋:調用父平臺方法
parentPlatformFactory(
providers.concat(extraProviders).concat({provide: marker, useValue: true}));
} else {
const injectedProviders: StaticProvider[] =
providers.concat(extraProviders).concat({provide: marker, useValue: true});
// 註釋:Injector.create建立平臺實例,並獲取設置爲全局平臺實例
createPlatform(Injector.create({providers: injectedProviders, name: desc}));
}
}
return assertPlatform(marker);
};
}
複製代碼
該方法接受三個參數:
parentPlatformFactory: ((extraProviders?: StaticProvider[]) => PlatformRef) | null
返回父平臺工廠實例的方法
name: string
平臺的名字
providers: StaticProvider[] = []
DI的服務提供者
首先經過 InjectionToken
建立一個 Platform: ${name}
的值提供商
而後返回一個方法,接受服務提供者 extraProviders?: StaticProvider[]
,返回一個平臺實例 PlatformRef
createPlatformFactory
返回的方法
AllowMultipleToken
這個容許多個令牌的服務提供者
parentPlatformFactory
存在,則合併服務提供商並遞歸調用 parentPlatformFactory
parentPlatformFactory
不存在,則使用注入器建立實例方法 Injector.create
建立實例平臺實例並用 createPlatform
設置爲全局的平臺實例assertPlatform
確認 IOC 容器中存在 該 marker
的平臺實例並返回因此建立平臺實例的順序上,應該是 合併 browserDynamic 的 provider => 合併 coreDynamic 的 provider => 合併 provider 並建立 core
大概用人話描述就是:
Factory
Factory
就把調用 Factory
時傳入的 Provider
和調用 createPlatformFactory
傳入的 Provider
合併,而後調用父 Factory
Factory
,先建立一個 Injector
,而後去建立 PlatformRef
實例angular/packages/core/src/application_ref.ts
let _platform: PlatformRef;
/** * Creates a platform. * Platforms have to be eagerly created via this function. * * @publicApi */
export function createPlatform(injector: Injector): PlatformRef {
if (_platform && !_platform.destroyed &&
!_platform.injector.get(ALLOW_MULTIPLE_PLATFORMS, false)) {
throw new Error(
'There can be only one platform. Destroy the previous one to create a new one.');
}
_platform = injector.get(PlatformRef);
// 註釋:初始化平臺時將執行的函數,平臺browserDynamic提供
const inits = injector.get(PLATFORM_INITIALIZER, null);
if (inits) inits.forEach((init: any) => init());
return _platform;
}
複製代碼
_platform
是全局的惟一平臺實例。
建立平臺實例關鍵方法,傳入服務注入器實例 injector
返回平臺實例:
injector.get(PLATFORM_INITIALIZER, null)
獲取初始化平臺時須要執行的函數並執行回過頭看 platformBrowserDynamic
:
angular/packages/platform-browser-dynamic/src/platform-browser-dynamic.ts
/** * @publicApi */
export const platformBrowserDynamic = createPlatformFactory(
platformCoreDynamic, 'browserDynamic', INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS);
複製代碼
重點來了:INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS
這個 providers
究竟提供了什麼服務?
angular/packages/platform-browser-dynamic/src/platform_providers.ts
/** * @publicApi */
export const INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS: StaticProvider[] = [
INTERNAL_BROWSER_PLATFORM_PROVIDERS, // 註釋:此處會注入初始化的幾個方法
{
provide: COMPILER_OPTIONS,
useValue: {providers: [{provide: ResourceLoader, useClass: ResourceLoaderImpl, deps: []}]},
multi: true
},
{provide: PLATFORM_ID, useValue: PLATFORM_BROWSER_ID},
];
複製代碼
除了 COMPILER_OPTIONS
和 PLATFORM_ID
,大概重點就是 INTERNAL_BROWSER_PLATFORM_PROVIDERS
了吧。
INTERNAL_BROWSER_PLATFORM_PROVIDERS
來自 @angular/platform-browser
:
angular/packages/platform-browser/src/browser.ts
export const INTERNAL_BROWSER_PLATFORM_PROVIDERS: StaticProvider[] = [
{provide: PLATFORM_ID, useValue: PLATFORM_BROWSER_ID},
{provide: PLATFORM_INITIALIZER, useValue: initDomAdapter, multi: true}, // 註釋:初始化的方法
{provide: PlatformLocation, useClass: BrowserPlatformLocation, deps: [DOCUMENT]},
{provide: DOCUMENT, useFactory: _document, deps: []},
];
複製代碼
@angular/platform-browser
提供了一些瀏覽器端的ng實現:
PLATFORM_INITIALIZER
是初始化須要執行的方法集合 這個很重要DOCUMENT
瀏覽器端的 document
,_document
工廠方法返回 document
在上面,createPlatform
的時候,會 const inits = injector.get(PLATFORM_INITIALIZER, null); if (inits) inits.forEach((init: any) => init());
依次執行 PLATFORM_INITIALIZER
注入的工廠方法。
那麼來看看 initDomAdapter
吧:
angular/packages/platform-browser/src/browser.ts
export function initDomAdapter() {
BrowserDomAdapter.makeCurrent();
BrowserGetTestability.init();
}
複製代碼
BrowserDomAdapter.makeCurrent();
經過 BrowserDomAdapter
的靜態方法實例化一個 BrowserDomAdapter
全局DOM適配器 ,具體就是實現並封裝了一些在瀏覽器端的方法,具體的能夠看 angular/packages/platform-browser/src/browser/browser_adapter.ts
中的 class BrowserDomAdapter extends GenericBrowserDomAdapter
BrowserGetTestability.init();
則是初始化 angular 的測試,這個就沒看了回過頭看下,在建立 platformBrowserDynamic
時候,傳入了返回父平臺實例的方法 platformCoreDynamic
angular/packages/platform-browser-dynamic/src/platform_core_dynamic.ts
import {COMPILER_OPTIONS, CompilerFactory, PlatformRef, StaticProvider, createPlatformFactory, platformCore} from '@angular/core';
import {JitCompilerFactory} from './compiler_factory';
/** * A platform that included corePlatform and the compiler. * * @publicApi */
export const platformCoreDynamic = createPlatformFactory(platformCore, 'coreDynamic', [
{provide: COMPILER_OPTIONS, useValue: {}, multi: true},
{provide: CompilerFactory, useClass: JitCompilerFactory, deps: [COMPILER_OPTIONS]},
]);
複製代碼
platformCoreDynamic
又傳入了
@angular/core
的 平臺核心 platformCore
coreDynamic
COMPILER_OPTIONS
和 platformDynamic
的JIT編譯器工廠 JitCompilerFactory
重點來了
一塊兒看下 JitCompilerFactory
:
angular/packages/platform-browser-dynamic/src/compiler_factory.ts
/** * @publicApi */
export class JitCompilerFactory implements CompilerFactory {
private _defaultOptions: CompilerOptions[];
/* @internal */
constructor(defaultOptions: CompilerOptions[]) {
const compilerOptions: CompilerOptions = {
useJit: true,
defaultEncapsulation: ViewEncapsulation.Emulated,
missingTranslation: MissingTranslationStrategy.Warning,
};
this._defaultOptions = [compilerOptions, ...defaultOptions];
}
createCompiler(options: CompilerOptions[] = []): Compiler {
const opts = _mergeOptions(this._defaultOptions.concat(options));
const injector = Injector.create([
// 註釋:編譯器 Compiler 在這裏被替換成 CompilerImpl
COMPILER_PROVIDERS, {
provide: CompilerConfig,
useFactory: () => {
return new CompilerConfig({
// let explicit values from the compiler options overwrite options
// from the app providers
useJit: opts.useJit,
jitDevMode: isDevMode(),
// let explicit values from the compiler options overwrite options
// from the app providers
defaultEncapsulation: opts.defaultEncapsulation,
missingTranslation: opts.missingTranslation,
preserveWhitespaces: opts.preserveWhitespaces,
});
},
deps: []
},
opts.providers !
]);
return injector.get(Compiler);
}
}
複製代碼
編譯器在 COMPILER_PROVIDERS
做爲服務提供商被提供給注射器這裏很重要,後面的編譯器會大量用到:
angular/packages/platform-browser-dynamic/src/compiler_factory.ts
/** * A set of providers that provide `JitCompiler` and its dependencies to use for * template compilation. */
export const COMPILER_PROVIDERS = <StaticProvider[]>[
// 註釋:這裏也是一個核心點-編譯反射器
{provide: CompileReflector, useValue: new JitReflector()},
// 註釋:ResourceLoader- 資源加載器
{provide: ResourceLoader, useValue: _NO_RESOURCE_LOADER},
// 註釋:jit 摘要解析器
{provide: JitSummaryResolver, deps: []},
// 註釋:摘要解析器
{provide: SummaryResolver, useExisting: JitSummaryResolver},
{provide: Console, deps: []},
// 註釋:語法分析器
{provide: Lexer, deps: []},
// 註釋:解析器器
{provide: Parser, deps: [Lexer]},
// 註釋:基本的HTML解析器
{
provide: baseHtmlParser,
useClass: HtmlParser,
deps: [],
},
// 註釋:國際化的HTML解析器
{
provide: I18NHtmlParser,
useFactory: (parser: HtmlParser, translations: string | null, format: string,
config: CompilerConfig, console: Console) => {
translations = translations || '';
const missingTranslation =
translations ? config.missingTranslation ! : MissingTranslationStrategy.Ignore;
// 註釋:new 國際化的HTML解析器
return new I18NHtmlParser(parser, translations, format, missingTranslation, console);
},
deps: [
baseHtmlParser,
[new Optional(), new Inject(TRANSLATIONS)],
[new Optional(), new Inject(TRANSLATIONS_FORMAT)],
[CompilerConfig],
[Console],
]
},
{
provide: HtmlParser,
useExisting: I18NHtmlParser,
},
// 註釋:模板解析器
{
provide: TemplateParser, deps: [CompilerConfig, CompileReflector,
Parser, ElementSchemaRegistry,
I18NHtmlParser, Console]
},
{ provide: JitEvaluator, useClass: JitEvaluator, deps: [] },
// 註釋:指令規範器
{ provide: DirectiveNormalizer, deps: [ResourceLoader, UrlResolver, HtmlParser, CompilerConfig]},
// 註釋:元數據解析器
{ provide: CompileMetadataResolver, deps: [CompilerConfig, HtmlParser, NgModuleResolver,
DirectiveResolver, PipeResolver,
SummaryResolver,
ElementSchemaRegistry,
DirectiveNormalizer, Console,
[Optional, StaticSymbolCache],
CompileReflector,
[Optional, ERROR_COLLECTOR_TOKEN]]},
DEFAULT_PACKAGE_URL_PROVIDER,
// 註釋:樣式編譯器
{ provide: StyleCompiler, deps: [UrlResolver]},
// 註釋:view 編譯器
{ provide: ViewCompiler, deps: [CompileReflector]},
// 註釋:NgModule編譯器
{ provide: NgModuleCompiler, deps: [CompileReflector] },
// 註釋:編譯器配置項目
{ provide: CompilerConfig, useValue: new CompilerConfig()},
// 註釋:JIT時,Compiler的服務供應商 CompilerImpl
{ provide: Compiler, useClass: CompilerImpl, deps: [Injector, CompileMetadataResolver,
TemplateParser, StyleCompiler,
ViewCompiler, NgModuleCompiler,
SummaryResolver, CompileReflector, JitEvaluator, CompilerConfig,
Console]},
// 註釋:DOM schema
{ provide: DomElementSchemaRegistry, deps: []},
// 註釋:Element schema
{ provide: ElementSchemaRegistry, useExisting: DomElementSchemaRegistry},
// 註釋:URL解析器
{ provide: UrlResolver, deps: [PACKAGE_ROOT_URL]},
// 註釋:指令解析器
{ provide: DirectiveResolver, deps: [CompileReflector]},
// 註釋:管道解析器
{ provide: PipeResolver, deps: [CompileReflector]},
// 註釋:模塊解析器
{ provide: NgModuleResolver, deps: [CompileReflector]},
];
複製代碼
最後,其實也是建立了一個 injector
,而後獲取了 編譯器實例 Compiler
,因此:
大概就是 @angular/platform-browser-dynamic
提供 JIT 編譯 的緣由了吧。
angular/packages/core/src/platform_core_providers.ts
import {PlatformRef, createPlatformFactory} from './application_ref';
import {PLATFORM_ID} from './application_tokens';
import {Console} from './console';
import {Injector, StaticProvider} from './di';
import {TestabilityRegistry} from './testability/testability';
const _CORE_PLATFORM_PROVIDERS: StaticProvider[] = [
// Set a default platform name for platforms that don't set it explicitly.
{provide: PLATFORM_ID, useValue: 'unknown'},
// 註釋:在這裏 PlatformRef 被加入了 injector 並在 createPlatformFactory 中實例化
{provide: PlatformRef, deps: [Injector]},
{provide: TestabilityRegistry, deps: []},
{provide: Console, deps: []},
];
/** * This platform has to be included in any other platform * * @publicApi */
export const platformCore = createPlatformFactory(null, 'core', _CORE_PLATFORM_PROVIDERS);
複製代碼
platformCore
則是建立了一個返回根平臺工廠實例的方法,並設置了4個基礎的DI的服務提供者
PLATFORM_ID
平臺idPlatformRef
在這裏 PlatformRef
被加入了 injector
並在後續的 createPlatformFactory
中經過 createPlatform(Injector.create({providers: injectedProviders, name: desc}));
平臺實例會被實例化TestabilityRegistry
可測試性註冊表 測試相關Console
頗有意思 angular 把 Console 做爲服務注入了DI,可是 Console 只實現了 log和warn兩個方法angular/packages/core/src/application_ref.ts
@Injectable()
export class PlatformRef {
private _modules: NgModuleRef<any>[] = [];
private _destroyListeners: Function[] = [];
private _destroyed: boolean = false;
/** @internal */
constructor(private _injector: Injector) {}
bootstrapModuleFactory<M>(moduleFactory: NgModuleFactory<M>, options?: BootstrapOptions):
Promise<NgModuleRef<M>> {
...
}
bootstrapModule<M>(
moduleType: Type<M>, compilerOptions: (CompilerOptions&BootstrapOptions)|
Array<CompilerOptions&BootstrapOptions> = []): Promise<NgModuleRef<M>> {
// 註釋:bootstrapModule` 首先經過 `optionsReducer` 遞歸 reduce 將編譯器選項 `compilerOptions` 拍平爲對象
const options = optionsReducer({}, compilerOptions);
// 註釋:這裏獲取到編譯後的模塊工廠,而後返回給 bootstrapModuleFactory建立模塊
return compileNgModuleFactory(this.injector, options, moduleType)
.then(moduleFactory => this.bootstrapModuleFactory(moduleFactory, options));
}
private _moduleDoBootstrap(moduleRef: InternalNgModuleRef<any>): void {
...
}
onDestroy(callback: () => void): void { this._destroyListeners.push(callback); }
get injector(): Injector { return this._injector; }
destroy() {
if (this._destroyed) {
throw new Error('The platform has already been destroyed!');
}
this._modules.slice().forEach(module => module.destroy());
this._destroyListeners.forEach(listener => listener());
this._destroyed = true;
}
get destroyed() { return this._destroyed; }
}
複製代碼
PlatformRef
就是平臺實例的類,有一些方法和屬性等,例如幾個關鍵的方法
bootstrapModule
引導根模塊的方法bootstrapModuleFactory
實例模塊的工廠方法,會運行 zone.js 並監聽事件destroy
銷燬平臺實例的方法這個咱們放到後文去說吧
調用 platformBrowserDynamic()
並生成平臺實例 PlatformRef
時大概經歷了這些:
createPlatformFactory
合併平臺 browserDynamic
的 providers
並觸發父級平臺 coreDynamic
的平臺工廠函數 平臺 browserDynamic
提供了 PLATFORM_INITIALIZER
平臺初始化函數和 BrowserDomAdapter
全局DOM適配器這個服務供應商createPlatformFactory
合併平臺 coreDynamic
的 providers
並觸發父級平臺 core
的平臺工廠函數 平臺 coreDynamic
提供了 JitCompilerFactory
運行時編譯器,JitCompilerFactory
又經過建立 COMPILER_PROVIDERS
建立了編譯器實例 因此 @angular/platform-browser-dynamic
提供 JIT運行時 編譯core
提供了 PlatformRef
平臺實例這個服務供應商core
無父級平臺,調用 Injector.create
建立 PlatformRef
實例,並賦值給全局惟一的平臺實例 _platform
createPlatform
建立 PlatformRef
的時候,實例化一個 BrowserDomAdapter
全局DOM適配器 ,具體就是實現並封裝了一些在瀏覽器端的方法PlatformRef
實例,並返回 PlatformRef
實例因此大概,@angular/platform-browser-dynamic
提供了運行時編譯,實現並封裝了瀏覽器方法