!
也想出现在这里? 联系我们
广告区块

【TypeScript】深入学习TypeScript命名空间

? TypeScript学习TypeScript从入门到精通

? 蓝桥杯真题解析蓝桥杯Web国赛真题解析

? 个人简介:即将大三的学生,热爱前端,热爱生活?
? 你的一键三连是我更新的最大动力❤️!

?分享博主自用牛客网?:一个非常全面的面试刷题求职网站,前端开发者必备的刷题网站,真的超级好用?


前言

最近博主一直在创作TypeScript的内容,所有的TypeScript文章都在我的TypeScript从入门到精通专栏里,每一篇文章都是精心打磨的优质好文,并且非常的全面和细致,期待你的订阅❤️

【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的命名空间就是以闭包的形式实现的,见下方第三部分实现原理

命名空间与类合并

先看一个例子:

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

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 的代码组织方式,它只是全局命名空间中命名的 JavaScript 对象,这使得命名空间成为一个非常简单易用的构造

就像所有全局命名空间污染一样,使用它很难识别组件依赖关系,尤其是在大型应用程序中

模块: 外部模块的简称,侧重代码的复用,一个模块里能够包含多个命名空间

模块可以包含代码和声明,依赖于模块加载器(如CommonJs/Require.js)或支持ES模块的运行,模块提供了更好的代码重用,更强的隔离性和更好的捆绑工具支持

同样值得注意的是,对于Node.js应用程序,模块是默认的,官方在现代代码中推荐模块而不是命名空间

EC6开始,模块是语言的原生部分,所有兼容的引擎实现都应该支持,因此,对于新项目,模块将是推荐的代码组织机制

结语

至此,TypeScript命名空间的内容就全部结束了,关注博主下篇更精彩!

博主的TypeScript从入门到精通专栏正在慢慢的补充之中,赶快关注订阅,与博主一起进步吧!期待你的三连支持。

参考资料:TypeScript官网

如果本篇文章对你有所帮助,还请客官一件四连!❤️

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/m0_51969330/article/details/126157458
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。
0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
有新私信 私信列表
搜索