前言
在【TypeScript】深入学习TypeScript模块化文章的最后我们提到了TypeScript
的命名空间namespaces
,这一节我们就将深入去学习namespaces
,并探讨它与模块的区别
文章目录
1、空间声明
在代码量较大的情况下,为了避免各种变量命名的冲突,可将相似功能的函数、类、接口等放置到命名空间之中
TypeScript
的命名空间使用namespaces
声明,它可以将代码包裹起来,并可以使用export
选择性的向外暴露指定内容:
namespace Ailjx {
// a没有使用export向外暴露,在外部无法访问
let a;
export const str = "Ailjx";
export type S = string;
export function f() {}
export class N {}
}
这里定义了一个名为Ailjx
的命名空间,在外部可以使用Ailjx.
的形式访问其内部通过export
暴露的成员:
const s: Ailjx.S = Ailjx.str;
Ailjx.f();
new Ailjx.N();
// 类型“typeof Ailjx”上不存在属性“a”
// console.log(Ailjx.a);// err
从上面可以看出TypeScript
的命名空间实际上就像一层大的容器,将内容包裹在其中,将其私有化,这就避免了外部其它变量与其内容命名冲突的问题
2、空间合并
多个相同名称的命名空间会自动进行合并,这就使得命名空间可以访问或修改同一名称下其它空间export
的成员:
namespace Ailjx {
export let a = 1;
}
namespace Ailjx {
a = 2;
export let b = 3;
}
console.log(Ailjx.a, Ailjx.b); // 2 3
没有export
的成员只在当前命名空间有效,不会受合并的影响:
namespace Ailjx {
// s没有export,它只在当前空间有效
let s = 0;
}
namespace Ailjx {
// 访问不到上个空间的s
s = 1; //❌❌❌err:找不到名称“s”
}
同一名称下的不同空间可以有相同名称的非export
成员,如下面的变量s
:
namespace A {
// s没有export,它只在当前空间有效
let s = 0;
export function getS1() {
console.log(s);
}
}
namespace A {
// s没有export,它只在当前空间有效
let s = 1;
export function getS2() {
console.log(s);
}
}
A.getS1(); // 0
A.getS2(); // 1
从这可以看出TypeScript
相同命名的空间并不只是简单的合并,这与闭包有些相似,然而当你查看上方代码编译后的js
文件,你就会发现TypeScript
的命名空间就是以闭包的形式实现的,见下方实现原理:
3、实现原理
上面命名空间A
编译后的js
代码:
"use strict";
"use strict";
var A;
(function (A) {
// s没有export,它只在当前空间有效
let s = 0;
function getS1() {
console.log(s);
}
A.getS1 = getS1;
})(A || (A = {}));
(function (A) {
// s没有export,它只在当前空间有效
let s = 1;
function getS2() {
console.log(s);
}
A.getS2 = getS2;
})(A || (A = {}));
A.getS1(); // 0
A.getS2(); // 1
再看一个export
暴露成员的命名空间:
namespace B {
export let s = 0;
}
namespace B {
s = 1;
}
编译后的js
:
"use strict";
var B;
(function (B) {
B.s = 0;
})(B || (B = {}));
(function (B) {
B.s = 1;
})(B || (B = {}));
有一定经验的大佬看到编译后的js
代码后,应该一下就能理解TypeScript
命名空间的实现原理
原理解读:
-
每一个命名空间的名称在
js
中就是一个全局变量(相同名称的空间用的是同一个变量,我将该变量称为名称变量,如上方的var A;
var B;
,名称变量实际就是一个存储export
内容的对象) -
每一个命名空间在
js
中都是一个传入其对应名称变量的立即执行函数 -
命名空间内通过
export
暴露的内容在js
中会挂载到其对应的名称变量中,这也就是同一名称不同空间的命名空间能够相互访问其内部export
成员的原因(因为它们接受的是同一个名称变量) -
命名空间内非
export
暴露的内容在js
中不会挂载到其对应的名称变量中,而只是在其立即执行函数中声明,并只对当前函数空间生效
4、模块化空间
命名空间结合TypeScript模块化,可以将其抽离到一个单独的ts
文件内,变成模块化的空间:
// src/a.ts
export namespace A {
export let s = 99;
}
引入并使用命名空间:
// src/hello.ts
import { A } from "./a";
console.log(A.s); // 99
5、空间别名
使用 import q = x.y.z
来为常用对象创建更短的名称:
namespace A {
export namespace B {
export class C {
constructor() {
console.log(999);
}
}
}
}
import MyC = A.B.C;
new MyC(); // 999 与new A.B.C()等价
new A.B.C(); // 999
没想到import
语法还能这样用,虽然很奇怪,但这在一些场景下应该会很实用
从这里也可以看出命名空间是可以嵌套使用的
6、命名空间与模块
命名空间: 相当于内部模块,主要用于组织代码,避免命名冲突
模块: 外部模块的简称,侧重代码的复用,一个模块里能够包含多个命名空间
结语
至此,TypeScript
命名空间的内容就全部结束了,关注博主下篇更精彩!
博主的TypeScript从入门到精通专栏正在慢慢的补充之中,赶快关注订阅,与博主一起进步吧!期待你的三连支持。
如果本篇文章对你有所帮助,还请客官一件四连!❤️