命名空间提供了一种在命名容器的层次结构中组织代码和声明的机制。 命名空间具有命名的成员,每个成员表示一个值,一个类型或一个命名空间,或其某种组合,这些成员可以是本地的也可以是导出的。 命名空间的主体对应于一次执行的功能,从而提供了一种机制,用于确保局部状态并确保隔离。 命名空间可以看作是立即调用的函数表达(IIFE)模式的形式化形式。
命名空间声明引入了具有命名空间含义的名称,对于实例化的命名空间,其名称在包含声明空间中也具有含义。
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”是作为类型名称还是表达式进行处理。
命名空间的主体对应于一次执行以初始化命名空间实例的函数。
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
导入别名声明用于为其他命名空间中的实体创建本地别名。
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修饰符时,将导出本地别名的所有含义。
导出声明声明了一个外部可访问的命名空间成员。 导出声明只是带有关键字export的常规声明。
命名空间的导出声明空间(第2.3节)的成员构成命名空间的导出成员集。 命名空间的实例类型是一种对象类型,其命名空间的导出成员集中的每个成员都有一个表示值的属性。
导出的成员取决于一组(可能为空)命名类型(第3.7节)。 这些命名类型必须至少具有与导出成员相同的可访问性,否则会发生错误。
成员所依赖的命名类型是在直接依赖的传递闭包中出现的命名类型,其定义如下:
具有根命名空间R(第2.3节)的命名类型T被称为至少与成员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。
命名空间是“开放式的”,并且相对于公共根(定义见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”的命名空间声明位于函数声明之后。
命名空间生成的JavaScript代码等效于以下内容:
var <NamespaceName>;
(function(<NamespaceName>) {
<NamespaceStatements>
})(<NamespaceName>||(<NamespaceName>={}));
其中,NamespaceName是命名空间的名称,NamespaceStatements是为命名空间主体中的语句生成的代码。 NamespaceName函数参数可以使用一个或多个下划线字符作为前缀,以确保该名称在函数体内是唯一的。 请注意,整个命名空间是作为立即执行的匿名函数生成的。 这样可以确保局部变量处于与周围环境隔离的自己的词汇环境中。 还要注意,生成的函数不会创建并返回命名空间实例,而是会扩展现有实例(可能是刚刚在函数调用中创建的实例)。 这样可以确保命名空间可以相互扩展。
导入语句生成以下形式的代码
var <Alias> = <EntityName>;
仅当在导入命名空间的主体中某处将导入的实体作为PrimaryExpression引用时,才会生成此代码。 如果仅将导入的实体引用为TypeName或NamespaceName,则不会生成任何内容。 这样可以确保在一个命名空间中声明的类型可以通过另一个命名空间中的导入别名进行引用,而不会产生运行时开销。
导出变量时,命名空间主体中对该变量的所有引用都将替换为
<NamespaceName>.<VariableName>
这有效地将变量提升为命名空间实例上的属性,并确保对变量的所有引用都变为对属性的引用。
导出函数,类,枚举或命名空间时,为实体生成的代码后跟形式为的赋值语句
<NamespaceName>.<EntityName> = <EntityName>;
这会将对实体的引用复制到命名空间实例的属性中。