👉 TypeScript学习:TypeScript从入门到精通
👉 蓝桥杯真题解析:蓝桥杯Web国赛真题解析
👉 个人简介:即将大三的学生,热爱前端,热爱生活🍬
👉 你的一键三连是我更新的最大动力❤️!
最近博主一直在创作
TypeScript
的内容,所有的TypeScript
文章都在我的TypeScript从入门到精通专栏里,每一篇文章都是精心打磨的优质好文,并且非常的全面和细致,期待你的订阅❤️
在【TypeScript】深入学习TypeScript模块化文章的最后我们提到了TypeScript
的命名空间namespaces
,这一节我们就将深入去学习namespaces
,并探讨它与模块的区别
在代码量较大的情况下,为了避免各种变量命名的冲突,可将相似功能的函数、类、接口等放置到命名空间之中
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
的命名空间实际上就像一层大的容器,将内容包裹在其中,将其私有化,这就避免了外部其它变量与其内容命名冲突的问题
多个相同名称的命名空间会自动进行合并,这就使得命名空间可以访问或修改同一名称下其它空间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
的命名空间就是以闭包的形式实现的,见下方第三部分实现原理
先看一个例子:
class Album {
label: Album.AlbumLabel;
}
namespace Album {
export class AlbumLabel {}
}
这给了用户提供了一种描述内部类的方法,合并成员的可见性规则与合并命名空间 中描述的相同,所以这里我们必须导出 AlbumLabel
类,以便
合并后的类能看到它,最终的结果是一个类在另一个类里面管理
你也可以使用命名空间来为现有的类添加更多的静态成员
JavaScript
的中可以在函数上添加属性来进一步扩展该函数,TypeScript
使用声明合并,以类型安全的方式构建这样的定义:
function fn(name: string): string {
return fn.prefix + name + fn.suffix;
}
namespace fn {
export let suffix = " !";
export let prefix = "Hello, ";
}
console.log(fn("Ailjx")); // "Hello, Ailjx !"
命名空间可用于扩展具有静态成员的枚举:
enum Color {
red = 1,
green = 2,
blue = 4,
}
namespace Color {
export function mixColor(colorName: string) {
if (colorName == "yellow") {
return Color.red + Color.green;
} else if (colorName == "white") {
return Color.red + Color.green + Color.blue;
} else if (colorName == "magenta") {
return Color.red + Color.blue;
} else if (colorName == "cyan") {
return Color.green + Color.blue;
}
}
}
console.log(Color.mixColor("white")); // 7
上面命名空间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
中不会挂载到其对应的名称变量中,而只是在其立即执行函数中声明,并只对当前函数空间生效
命名空间结合TypeScript模块化,可以将其抽离到一个单独的ts
文件内,变成模块化的空间:
// src/a.ts
export namespace A {
export let s = 99;
}
引入并使用命名空间:
// src/hello.ts
import { A } from "./a";
console.log(A.s); // 99
使用 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
语法还能这样用,虽然很奇怪,但这在一些场景下应该会很实用
从这里也可以看出命名空间是可以嵌套使用的
命名空间: 相当于内部模块,主要用于组织代码,避免命名冲突
命名空间是一种特定于
TypeScript
的代码组织方式,它只是全局命名空间中命名的JavaScript
对象,这使得命名空间成为一个非常简单易用的构造
就像所有全局命名空间污染一样,使用它很难识别组件依赖关系,尤其是在大型应用程序中
模块: 外部模块的简称,侧重代码的复用,一个模块里能够包含多个命名空间
模块可以包含代码和声明,依赖于模块加载器(如
CommonJs/Require.js
)或支持ES
模块的运行,模块提供了更好的代码重用,更强的隔离性和更好的捆绑工具支持
同样值得注意的是,对于
Node.js
应用程序,模块是默认的,官方在现代代码中推荐模块而不是命名空间
从
EC6
开始,模块是语言的原生部分,所有兼容的引擎实现都应该支持,因此,对于新项目,模块将是推荐的代码组织机制
至此,TypeScript
命名空间的内容就全部结束了,关注博主下篇更精彩!
博主的TypeScript从入门到精通专栏正在慢慢的补充之中,赶快关注订阅,与博主一起进步吧!期待你的三连支持。
参考资料:TypeScript官网
如果本篇文章对你有所帮助,还请客官一件四连!❤️