TypeScript学习笔记一:命名空间(译)
命名空间
命名空间提供了一种对命名容器结构组织代码和声明的机制。命名空间拥有本地或导出的命名成员,如值、类型、命名空间或者它们的组合。命名空间体相对于一个执行了一次的函数,从而提供了一种通过确定隔离的方式保留本地状态的机制。命名空间可被认为是立即执行方法(IIFE)模式的形式化。
10.1 命名声明
NamespaceDeclaration:
namespace IdentifierPath { NamespaceBody }
IdentifierPath:
BindingIdentifier
IdentifierPath . BindingIdentifier
我们用关键字”namespace“来声明命名空间,但为了兼容ts老版本,也可用”module“关键字来声明。
命名空间既可是实例化的也可是非实例化的。非实例化命名空间只包含接口类型,类型别名和其他非实例化命名空间。实例化命名空间则反之。直观上,实例化命名空间创建了一个命名空间实例,而非实例化命名空间则没有生成代码。
当命名空间标识符作为命名空间名引用时,它代表一个命名空间的容器和类型名。当标识符作为基本表达式引用时,它代表一个命名空间实例单例。例如:
namespace M {
export interface P { x: number; y: number; }
export var a = 1;
}
var p: M.P; // M 用作命名空间名
var m = M; // M 用作基本表达式
var x1 = M.a; // M 用作基本表达式
var x2 = m.a; // 与 M.a相同
var q: m.P; // Error
上例中,当“M"被用作基本表达式时,它代表一个具有成员”a"的对象实例,当“M"被用作命名空间名时,它代表了一个拥有类型成员”P"的容器。“var q:m.P”语句错误的原因是,“m”是一个变量,不能在类型名中引用。
如果在“M"声明中不导出变量“a",”M"即为非实例化命名空间,那么它就不能被作为基本表达式引用了。
用路径标识符方式声明的命名空间等同于嵌套的单一标识符声明的命名空间,但是后者最外层不会自动导出。例如:
namespace A.B.C {
export var x = 1;
}
等同于:
namespace A {
export namespace B {
export namespace C {
export var x = 1;
}
}
}
用“A.B.C”引入一个命名类型,同时引入一个可以用“A.B.C”表达式访问的构造器函数:
var c: A.B.C = new 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
~~!!!!!!!!!!!!!!
namespace A {
export interface X { s: string }
export var X: X;
}
namespace B {
interface A { n: number }
import Y = A; // 命名空间A的别名
import Z = A.X; // A.X类型和值的别名
var v: Z = Z;
}
在命名空间”B“中,”Y"是命名空间”A"的一个别名,而非本地的接口“A"的别名。然而,”Z"则代表一个接口类型和一个变量。
如果实体名的命名空间名部分引用了一个实例化的命名空间时,当被作为表达式计算时,命名空间名需要引用命名空间实例。
namespace A {
export interface X { s: string }
}
namespace B {
var A = 1;
import Y = A;
}
“Y"是非实例化命名空间“A"的一个本地别名。如果”A"变为一个实例化命名空间,例如在“A"声明中加入一个变量,那么上例中的”B"将导入出错,因为表达式”A“没有引用命名空间”A"的实例。
!!!!
10.4 导出声明
导出声明声明一个可被外部访问的命名空间成员。导出声明用”export“修饰。
命名空间的导出声明空间成员构成了命名空间导出成员集合。命名空间实例类型是一个属性的对象类型,这个属性为每个集合中的成员都赋值。
导出成员依赖于一个命名类型集合(可能为空)。这些命名类型必须是“至少可访问”的。
成员的命名类型取决于在传递闭包中发生的命名类型,其定义如下:
-
- 变量直接依赖于其类型注释指定的类型。
- 函数直接依赖于每一个参数指定的类型或返回类型注释。
- 类直接依赖于类型参数约束指定的类型,每一个类型引用被指定为一个基类或已实现的接口。每一个类型指定到一个构造器参数类型注释、公共成员变量类型注释、公共成员函数参数或返回类型注释、公共成员访问器参数或返回类型注释或者索引签名类型注释。
- 接口直接依赖于每一个指定为类型参数约束的类型。每一个类型引用被指定为一个基接口,对象类型被指定为它的内容体。
- 命名空间直接依赖于它的导出成员。
- 类型或对象类型直接依赖于任何层次的嵌套类型。
- 类型引用直接依赖于其引用的类型和每一个被指定为类型参数的类型。
命名类型“T"有一个根命名空间”R"被称之为“至少可访问”一个成员“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”所存在命名空间的父命名空间。
10.5 声明合并
命名空间是“开放式”的,相同名称的命名空间声明相关于作用于同一命名空间的根。例如,如下两个位于不同资源文件中的命名空间声明“outer”。
文件 a.ts:
namespace outer {
var local = 1; // 没有导出的变量
export var a = local; // outer.a
export namespace inner {
export var x = 10; // outer.inner.x
}
}
文件 b.ts
namespace outer {
var local = 2; //没有导出的变量
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;
};
}
声明合并对通过导入别名声明创建的本地别名不起作用。
声明合并也适用于函数、类或枚举声明:
- 当合并一个函数和命名空间,函数对象的类型与命名空间实例类型合并。函数的重载或实现提供调用签名,导出的命名空间成员提供联合类型的属性。
- 当合并一个类和命名空间,构造器函数的类型与命名空间实例类型合并。类构造器的重载或实现提供构造签名,类的静态成员和导出的命名空间成员提供联合类型的属性。静态类型成员和导出的命名空间成员不能命名冲突。
- 当合并一个枚举和命名空间,枚举对象的类型与命名空间实例类型合并。枚举成员和导出的命名空间成员提供联合类型的属性。枚举成员和导出的命名空间成员不能命名冲突。
当合并一个非环境函数或者类声明和一个非环境命名空间声明时,函数或类必须位于同一资源文件中的命名空间声明前面。这确保了共享的对象实例被创建为函数对象。(可以在对象创建后为其添加属性)。
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 代码生成
命名空间声明如下格式的js代码:
var <NamespaceName>;
(function(<NamespaceName>) {
<NamespaceStatements>
})(<NamespaceName>||(<NamespaceName>={}));
“NamespaceName”是命名空间名,“NamespaceStatements”是命名空间体的表达式。“NamespaceName”函数参数以一个或多个下划线(“_”)字符为前缀以避免和函数体中的名称冲突。整个命名空间被生产为一个直接执行的匿名函数, 为确保本地变量在它们各自的语义环境中。生成的函数不创建也不返回一个命名空间实例,而是扩展存在的实例(或许只在函数调用中被创建)。这种方式使命名空间能够被扩展。
导入表达式生成如下格式代码:
var <Alias> = <EntityName>;
上面的代码只有在被导入实体被作为基本表达式引用时才会生成。如果被导入实体只被作为类型名或命名空间名引用时,将不生成代码。这种方式使我们能够在一个命名空间中通过导入别名方式来引用另一个命名空间中声明的类型,而无运行时开销。
当一个变量被导入,此变量在命名空间体中的所有引用将被替代为:
<NamespaceName>.<VariableName>
这使变量被提升为命名空间实例中的属性,这确保了所有的变量引用变为属性引用。
当函数、类、枚举或者命名空间被导出时,实体生成的代码是一个赋值表达式:
<NamespaceName>.<EntityName> = <EntityName>;

浙公网安备 33010602011771号