资讯专栏INFORMATION COLUMN

试读angular源码第一章:开场与platformBrowserDynamic

RiverLi / 1064人阅读

摘要:而且大部分人一听说就会本能地避开。至于启动项目,都是这一行开始的。应用是模块化的,它拥有自己的模块化系统,称作。

开场来个自我介绍

angular 源码阅读

项目地址

文章地址

angular 版本:8.0.0-rc.4

欢迎看看我的类angular框架

关于为什么写这么一个项目

声明:仅仅为个人阅读源码的理解,不一定完全正确,还需要大佬的指点。

其实市面上很多关于 vue和react 的源码阅读,但是基本上没有看到关于 angular 系统性地源码阅读。

而且大部分人一听说 angular 就会本能地避开。

但其实不是的,在我眼里 angular 只是套用了很多后端已有的概念,比如 DI,比如 AOT 等。

之前我写过一个类 angular 的框架 InDiv,基本上实现了大多数 ng 的装饰器。

而且在写这个项目的时候,我从 angular 上学到了很多。

这次,则希望通过阅读 angular 的源代码,学习到更多谷歌在设计模式上的运用,学习到更多代码优化和结构的运用。

也有一点私心,希望更多人说 ng大法好 ,哈哈。

前提

希望看之前读者能先了解一下 typescripy 和 angular 的基础概念,因为文章里会出现大量的 DI,服务商啊这类词

    typescript

    angular文档

项目结构

项目下只有三个文件夹:angular docs 和 my-demo

- angular: 注释版angular的ts源代码
- docs: 文档位置
- my-demo: 启动的一个demo项目

通过 tsconfig 把 angular 别名设置到 angular这个文件夹,来阅读下 ts 版本的源码。

启动app

在浏览器端,每个 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都是由 bootstrapModule 根模块或主模块启动的。

Angular 应用是模块化的,它拥有自己的模块化系统,称作 NgModule

关于 NgModule

一个 NgModule 就是一个容器,用于存放一些内聚的代码块,这些代码块专注于某个应用领域、某个工作流或一组紧密相关的功能。

它可以包含一些组件、服务提供商或其它代码文件,其作用域由包含它们的 NgModule 定义。 它还可以导入一些由其它模块中导出的功能,并导出一些指定的功能供其它 NgModule 使用。

每个 Angular 应用都至少有一个 NgModule 类,也就是根模块,它习惯上命名为 AppModule,并位于一个名叫 app.module.ts 的文件中。

引导这个根模块就可以启动你的应用

当 bootstrap(引导)根模块之后,NgModule 会继而实例化元数据中 bootstrap

bootstrap 应用的主视图,称为根组件。它是应用中所有其它视图的宿主。只有根模块才应该设置这个 bootstrap 属性

platform

angular 抽象出 platform,来实现跨平台。

实例化 angular 根模块的 bootstrapModule 的方法在浏览器端来自 @angular/platform-browser-dynamic

其实除了 @angular/platform-browser-dynamic 之外还有 @angular/platform-browser

这两个模块的主要区别是编译方式的不同, platform-browser-dynamic 提供 JIT 编译,也就是说编译在浏览器内完成,而 platform-browser 提供 AOT 编译,编译在本地完成。

至于区别

platformBrowserDynamic

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 的函数

createPlatformFactory

angular/packages/core/src/application_ref.ts

/**
 * Creates a factory for a platform
 *
 * @publicApi
 */
export function createPlatformFactory(
    parentPlatformFactory: ((extraProviders");

该方法接受三个参数:

    parentPlatformFactory: ((extraProviders"); 返回父平台工厂实例的方法

    name: string 平台的名字

    providers: StaticProvider[] = [] DI的服务提供者

    首先通过 InjectionToken 创建一个 Platform: ${name} 的值提供商

    然后返回一个方法,接受服务提供者 extraProviders");,返回一个平台实例 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 实例

createPlatform

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);
  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_OPTIONSPLATFORM_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

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

    2个静态服务提供者:编译选项 COMPILER_OPTIONSplatformDynamic 的JIT编译器工厂 JitCompilerFactory

platformCore

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 平台id

    PlatformRef 在这里 PlatformRef 被加入了 injector 并在后续的 createPlatformFactory 中通过 createPlatform(Injector.create({providers: injectedProviders, name: desc})); 平台实例会被实例化

    TestabilityRegistry 可测试性注册表 测试相关

    Console 很有意思 angular 把 Console 作为服务注入了DI,但是 Console 只实现了 log和warn两个方法

PlatformRef

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(moduleFactory: NgModuleFactory, options");Promise> {
        ...
  }

  bootstrapModule(
      moduleType: Type, compilerOptions: (CompilerOptions&BootstrapOptions)|
      Array = []): Promise> {
    const options = optionsReducer({}, compilerOptions);
    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 合并平台 browserDynamicproviders 并触发父级平台 coreDynamic 的平台工厂函数

    调用 createPlatformFactory 合并平台 coreDynamicproviders 并触发父级平台 core 的平台工厂函数

    由于平台 core 无父级平台,调用 Injector.create 创建 PlatformRef 实例,并赋值给全局唯一的平台实例 _platform

    createPlatform 创建 PlatformRef 的时候,实例化一个 BrowserDomAdapter 全局DOM适配器 ,具体就是实现并封装了一些在浏览器端的方法

    最后断言,确认存在 PlatformRef 实例,并返回 PlatformRef 实例

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/7881.html

相关文章

  • angular源码分析之platformBrowserDynamic

    摘要:生成项目后,中的代码这里调用了包中导出的函数这个函数是浏览器平台的工厂函数执行会返回浏览器平台的实例函数是通过函数创建的这个函数接收个参数父平台工厂函数平台名称服务提供商的数组顾名思义函数的作用是创建平台工厂的函数在框架被加 cli生成项目后,main.ts中的代码 import { enableProdMode } from @angular/core; import { platf...

    FWHeart 评论0 收藏0
  • angular源码分析之platformBrowserDynamic

    摘要:生成项目后,中的代码这里调用了包中导出的函数这个函数是浏览器平台的工厂函数执行会返回浏览器平台的实例函数是通过函数创建的这个函数接收个参数父平台工厂函数平台名称服务提供商的数组顾名思义函数的作用是创建平台工厂的函数在框架被加 cli生成项目后,main.ts中的代码 import { enableProdMode } from @angular/core; import { platf...

    zhoutao 评论0 收藏0
  • Angular 2.x 从0到1 (一)史上最简单的Angular2教程

    摘要:官方支持微软出品,是的超集,是的强类型版本作为首选编程语言,使得开发脚本语言的一些问题可以更早更方便的找到。第一个组件那么我们来为我们的增加一个吧,在命令行窗口输入。引导过程通过在中引导来启动应用。它们的核心就是。 第一节:Angular 2.0 从0到1 (一)第二节:Angular 2.0 从0到1 (二)第三节:Angular 2.0 从0到1 (三) 第一章:认识Angular...

    tuniutech 评论0 收藏0
  • 从安装认识Angular 2

    摘要:首先,要确认安装了,并且创建了目录并执行初始化。想必看见上面的那么多包会一脸懵逼,没关系,我第一眼看见这些的那刻,和你现在的表情一样,下面在适当的时候我会逐个解释的,你只需要相信我上面的包都是跑所必须的,缺一不可。 关于介绍,只说一句:Angular 2是一个强大、全面、庞大的MVVM框架。 安装 安装,也算是一个坎,因为你需要安装一大堆东西,却不知道每个东西是做什么的,尽管有Angu...

    xietao3 评论0 收藏0
  • angular2初入眼帘之-多components协作

    摘要:我们使用了模式书写,并引入了思想,这些以前只在里见到的设计,现在里也有体现,并且在本章中会着重讲解多的协作。如果之前写过,那对于这种书写方式一定无比熟悉。每次数据的变更,无论是还是,都将变化冒泡到,然后由再向下逐级推送各组件是否重绘。 前集回顾 在上一章里我们讲了如何在angular2下开发一个component(还没做的赶紧去学吧)。我们使用了Unidirectional Data ...

    dreamans 评论0 收藏0

发表评论

0条评论

RiverLi

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<