• 【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 {}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这里定义了一个名为Ailjx的命名空间,在外部可以使用Ailjx.的形式访问其内部通过export暴露的成员:

    const s: Ailjx.S = Ailjx.str;
    Ailjx.f();
    new Ailjx.N();
    // 类型“typeof Ailjx”上不存在属性“a”
    // console.log(Ailjx.a);// err
    
    • 1
    • 2
    • 3
    • 4
    • 5

    从上面可以看出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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    没有export的成员只在当前命名空间有效,不会受合并的影响:

    namespace Ailjx {
        // s没有export,它只在当前空间有效
        let s = 0;
    }
    namespace Ailjx {
        // 访问不到上个空间的s
        s = 1; //❌❌❌err:找不到名称“s”
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    同一名称下的不同空间可以有相同名称的非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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    从这可以看出TypeScript相同命名的空间并不只是简单的合并,这与闭包有些相似,然而当你查看上方代码编译后的js文件,你就会发现TypeScript的命名空间就是以闭包的形式实现的,见下方第三部分实现原理

    命名空间与类合并

    先看一个例子:

    class Album {
        label: Album.AlbumLabel;
    }
    namespace Album {
        export class AlbumLabel {}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这给了用户提供了一种描述内部类的方法,合并成员的可见性规则与合并命名空间 中描述的相同,所以这里我们必须导出 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 !"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    命名空间与枚举合并

    命名空间可用于扩展具有静态成员的枚举:

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    再看一个export暴露成员的命名空间:

    namespace B {
        export let s = 0;
    }
    namespace B {
        s = 1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    编译后的js

    "use strict";
    var B;
    (function (B) {
        B.s = 0;
    })(B || (B = {}));
    (function (B) {
        B.s = 1;
    })(B || (B = {}));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    有一定经验的大佬看到编译后的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;
    }
    
    • 1
    • 2
    • 3
    • 4

    引入并使用命名空间:

    // src/hello.ts
    import { A } from "./a";
    console.log(A.s); // 99
    
    • 1
    • 2
    • 3

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    没想到import语法还能这样用,虽然很奇怪,但这在一些场景下应该会很实用

    从这里也可以看出命名空间是可以嵌套使用

    6、命名空间与模块

    命名空间: 相当于内部模块,主要用于组织代码,避免命名冲突

    命名空间是一种特定于 TypeScript 的代码组织方式,它只是全局命名空间中命名的 JavaScript 对象,这使得命名空间成为一个非常简单易用的构造

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

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

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

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

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

    结语

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

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

    参考资料:TypeScript官网

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

  • 相关阅读:
    Cmake常用命令(一)
    SpringMvc(2)RequestMapping注解
    linux驱动之ioctl详解
    java毕业设计房屋中介管理Mybatis+系统+数据库+调试部署
    Android JNI笔记
    element表格跨页全选实现(二)之后端分页
    优选丨 5 大用例设计笔试大题,附超详细解析
    了解Vue
    面试突击78:@Autowired 和 @Resource 有什么区别?
    基于单片机的红外遥控解码程序设计与实现
  • 原文地址:https://blog.csdn.net/m0_51969330/article/details/126157458