[TypeScript] 编程实践之1: Google的TypeScript代码风格10:命名空间

TypeScript语言规范

  • 10 命名空间
    • 10.1 命名空间声明
    • 10.2 命名空间体
    • 10.3 导入别名声明
    • 10.4 导出声明
    • 10.5 声明合并
    • 10.6 代码生成

10 命名空间

命名空间提供了一种在命名容器的层次结构中组织代码和声明的机制。 命名空间具有命名的成员,每个成员表示一个值,一个类型或一个命名空间,或其某种组合,这些成员可以是本地的也可以是导出的。 命名空间的主体对应于一次执行的功能,从而提供了一种机制,用于确保局部状态并确保隔离。 命名空间可以看作是立即调用的函数表达(IIFE)模式的形式化形式。

10.1 命名空间声明

命名空间声明引入了具有命名空间含义的名称,对于实例化的命名空间,其名称在包含声明空间中也具有含义。

NamespaceDeclaration:
	namespace IdentifierPath { NamespaceBody }

IdentifierPath:
	BindingIdentifier
	IdentifierPath . BindingIdentifier

命名空间是使用namespace关键字声明的,但是为了向后兼容TypeScript的早期版本,也可以使用module关键字。

命名空间是实例化的或非实例化的。 未实例化的命名空间是仅包含接口类型,类型别名和其他未实例化的命名空间的命名空间。 实例化命名空间是不满足此定义的命名空间。 用直观的术语来说,实例化的命名空间是为其创建命名空间实例的实例,而非实例化的命名空间是为其不生成代码的实例命名空间。

当命名空间标识符被引用为NamespaceName(第3.8.2节)时,它表示命名空间和类型名称的容器,而当命名空间标识符被引用为PrimaryExpression(第4.3节)时,则表示单例命名空间实例。 例如:

namespace M {  
    export interface P { x: number; y: number; }  
    export var a = 1;  
}

var p: M.P;             // M used as NamespaceName  
var m = M;              // M used as PrimaryExpression  
var x1 = M.a;           // M used as PrimaryExpression  
var x2 = m.a;           // Same as M.a  
var q: m.P;             // Error

上面,当’M’被用作PrimaryExpression时,它表示具有单个成员’a’的对象实例,而当’M’被用作NamespaceName时,则表示具有单个类型成员’P’的容器。 该示例的最后一行是一个错误,因为’m’是一个不能在类型名称中引用的变量。

如果上面的“ M”声明已排除导出的变量“ a”,则“ M”将是未实例化的命名空间,并且将“ M”作为PrimaryExpression引用将是错误的。

指定一个具有多个标识符的IdentifierPath的命名空间声明等效于一系列嵌套的单标识符命名空间声明,其中除最外层的所有其他命名空间声明都会自动导出。 例如:

namespace A.B.C {  
    export var x = 1;  
}

对应于

namespace A {  
    export namespace B {  
        export namespace C {  
            export var x = 1;  
        }  
    }  
}

由命名空间和命名类型名称形成的层次结构部分地反映了由命名空间实例和成员形成的层次结构。 这个例子

namespace A {  
    export namespace B {  
        export class C { }  
    }  
}

引入了具有限定名称“ A.B.C”的命名类型,还引入了可使用表达式“ A.B.C”访问的构造函数。 因此,在示例中

var c: A.B.C = new A.B.C();

实际上,“ A.B.C”的两次出现是指不同的实体。 出现的上下文确定“ A.B.C”是作为类型名称还是表达式进行处理。

10.2 命名空间体

命名空间的主体对应于一次执行以初始化命名空间实例的函数。

NamespaceBody:
	NamespaceElementsopt

NamespaceElements:
	NamespaceElement
	NamespaceElements NamespaceElement

NamespaceElement:
	Statement
	LexicalDeclaration
	FunctionDeclaration
	GeneratorDeclaration
	ClassDeclaration
	InterfaceDeclaration
	TypeAliasDeclaration
	EnumDeclaration
	NamespaceDeclaration
	AmbientDeclaration
	ImportAliasDeclaration
	ExportNamespaceElement

ExportNamespaceElement:
	export VariableStatement
	export LexicalDeclaration
	export FunctionDeclaration
	export GeneratorDeclaration
	export ClassDeclaration
	export InterfaceDeclaration
	export TypeAliasDeclaration
	export EnumDeclaration
	export NamespaceDeclaration
	export AmbientDeclaration
	export ImportAliasDeclaration

10.3 导入别名声明

导入别名声明用于为其他命名空间中的实体创建本地别名。

ImportAliasDeclaration:
	import BindingIdentifier = EntityName ;

EntityName:
	NamespaceName
	NamespaceName . IdentifierReference

由单个标识符组成的EntityName被解析为NamespaceName,因此需要引用一个命名空间。 生成的本地别名引用给定的命名空间,并且本身被分类为命名空间。

由多个标识符组成的EntityName解析为NamespaceName,后跟一个在给定命名空间中命名导出实体的标识符。 生成的本地别名具有所引用实体的所有含义。 (实体名称最多可以具有三种不同的含义-值,类型和命名空间。)实际上,就好像是使用本地别名在本地声明了导入的实体。

在这个例子中

namespace A {  
    export interface X { s: string }  
    export var X: X;  
}

namespace B {  
    interface A { n: number }  
    import Y = A;    // Alias for namespace A  
    import Z = A.X;  // Alias for type and value A.X  
    var v: Z = Z;  
}

在“ B”中,“ Y”仅是命名空间“ A”的别名,而不是本地接口“ A”,而“ Z”是所有“ A.X”导出含义的别名,因此表示两种接口类型 和一个变量。

如果EntityName的NamespaceName部分引用了实例化的命名空间,则在作为表达式求值时,需要NamespaceName引用命名空间实例。 在这个例子中

namespace A {  
    export interface X { s: string }  
}

namespace B {  
    var A = 1;  
    import Y = A;  
}

“ Y”是未实例化命名空间“ A”的本地别名。 如果更改了“ A”的声明,以使“ A”成为实例化的命名空间,例如通过在“ A”中包含变量声明,则上面“ B”中的import语句将是错误的,因为表达式“ A”没有 不能引用命名空间“ A”的命名空间实例。

当import语句包含export修饰符时,将导出本地别名的所有含义。

10.4 导出声明

导出声明声明了一个外部可访问的命名空间成员。 导出声明只是带有关键字export的常规声明。

命名空间的导出声明空间(第2.3节)的成员构成命名空间的导出成员集。 命名空间的实例类型是一种对象类型,其命名空间的导出成员集中的每个成员都有一个表示值的属性。

导出的成员取决于一组(可能为空)命名类型(第3.7节)。 这些命名类型必须至少具有与导出成员相同的可访问性,否则会发生错误。

成员所依赖的命名类型是在直接依赖的传递闭包中出现的命名类型,其定义如下:

  • 变量直接取决于其类型注释中指定的类型。
  • 函数直接取决于参数或返回类型注释中指定的每个Type。
  • 一个类直接依赖于指定为类型参数约束的每个Type,指定为基类或已实现接口的每个TypeReference以及构造函数参数类型注释,公共成员变量类型注释,公共成员函数参数或返回类型注释中指定的每个Type ,公共成员访问器参数或返回类型注释或索引签名类型注释。
  • 接口直接取决于指定为类型参数约束的每个Type,指定为基本接口的每个TypeReference和指定其主体的ObjectType。
  • 命名空间直接取决于其导出的成员。
  • Type或ObjectType直接取决于在任何嵌套级别的类型中发生的每个TypeReference。
  • TypeReference直接取决于它引用的类型以及指定为类型实参的每个Type。

具有根命名空间R(第2.3节)的命名类型T被称为至少与成员M一样可访问,如果

  • R是全局命名空间或模块,或者
  • R是M的父命名空间链中的命名空间。

在例子中

interface A { x: string; }

namespace M {  
    export interface B { x: A; }  
    export interface C { x: B; }  
    export function foo(c: C) {}  
}

“ foo”函数取决于命名的类型“ A”,“ B”和“ C”。 为了导出“ foo”,还必须导出“ B”和“ C”,否则它们将至少不如“ foo”可访问。 “ A”接口至少已经与“ foo”具有相同的可访问性,因为在foo的命名空间的父命名空间中声明了I t。

10.5 声明合并

命名空间是“开放式的”,并且相对于公共根(定义见2.3节),具有相同限定名称的命名空间声明构成单个命名空间。 例如,命名空间“外部”的以下两个声明可能位于单独的源文件中。

文件a.ts:

namespace outer {  
    var local = 1;           // Non-exported local variable  
    export var a = local;    // outer.a  
    export namespace inner {  
        export var x = 10;   // outer.inner.x  
    }  
}

b.ts文件:

namespace outer {  
    var local = 2;           // Non-exported local variable  
    export var b = local;    // outer.b  
    export namespace inner {  
        export var y = 20;   // outer.inner.y  
    }  
}

假设两个源文件是同一程序的一部分,则这两个声明将以全局命名空间作为它们的公共根,因此将对同一个命名空间实例有所贡献,其实例类型为:

{  
    a: number;  
    b: number;  
    inner: {  
        x: number;  
        y: number;  
    };  
}

声明合并不适用于由导入别名声明创建的本地别名。换句话说,不可能在同一命名空间主体中具有相同名称的导入别名声明和命名空间声明。

TODO:别名解析。

声明合并还扩展到命名空间声明,该命名空间声明具有相对于作为函数,类或枚举声明的公共根的相同限定名称:

  • 合并功能和命名空间时,功能对象的类型将与命名空间的实例类型合并。实际上,函数的重载或实现提供了调用签名,命名空间的导出成员提供了组合类型的属性。
  • 合并类和命名空间时,构造函数对象的类型将与命名空间的实例类型合并。实际上,类构造函数的重载或实现提供了构造签名,而类的静态成员和命名空间的导出成员提供了组合类型的属性。具有相同名称的静态类成员和导出的命名空间成员是错误的。
  • 合并枚举和命名空间时,枚举对象的类型将与命名空间的实例类型合并。实际上,枚举的成员和命名空间的导出成员提供了组合类型的属性。枚举成员和导出的命名空间成员具有相同的名称是错误的。

合并非环境函数或类声明和非环境命名空间声明时,该函数或类声明必须位于同一源文件中的命名空间声明之前。这样可以确保将共享库实例创建为功能对象。 (虽然可以在创建对象后向其添加属性,但在事实创建后不能将对象“调用”。)

这个例子

interface Point {  
    x: number;  
    y: number;  
}

function point(x: number, y: number): Point {  
    return { x: x, y: y };  
}

namespace point {  
    export var origin = point(0, 0);  
    export function equals(p1: Point, p2: Point) {  
        return p1.x == p2.x && p1.y == p2.y;  
    }  
}

var p1 = point(0, 0);  
var p2 = point.origin;  
var b = point.equals(p1, p2);

将’point’声明为具有两个属性’origin’和’equals’的函数对象。 请注意,“ point”的命名空间声明位于函数声明之后。

10.6 代码生成

命名空间生成的JavaScript代码等效于以下内容:

var <NamespaceName>;  
(function(<NamespaceName>) {  
    <NamespaceStatements>  
})(<NamespaceName>||(<NamespaceName>={}));

其中,NamespaceName是命名空间的名称,NamespaceStatements是为命名空间主体中的语句生成的代码。 NamespaceName函数参数可以使用一个或多个下划线字符作为前缀,以确保该名称在函数体内是唯一的。 请注意,整个命名空间是作为立即执行的匿名函数生成的。 这样可以确保局部变量处于与周围环境隔离的自己的词汇环境中。 还要注意,生成的函数不会创建并返回命名空间实例,而是会扩展现有实例(可能是刚刚在函数调用中创建的实例)。 这样可以确保命名空间可以相互扩展。

导入语句生成以下形式的代码

var <Alias> = <EntityName>;

仅当在导入命名空间的主体中某处将导入的实体作为PrimaryExpression引用时,才会生成此代码。 如果仅将导入的实体引用为TypeName或NamespaceName,则不会生成任何内容。 这样可以确保在一个命名空间中声明的类型可以通过另一个命名空间中的导入别名进行引用,而不会产生运行时开销。

导出变量时,命名空间主体中对该变量的所有引用都将替换为

<NamespaceName>.<VariableName>

这有效地将变量提升为命名空间实例上的属性,并确保对变量的所有引用都变为对属性的引用。

导出函数,类,枚举或命名空间时,为实体生成的代码后跟形式为的赋值语句

<NamespaceName>.<EntityName> = <EntityName>;

这会将对实体的引用复制到命名空间实例的属性中。

你可能感兴趣的:(程序设计语言)