TypeScript 完全指南:从基础到高级类型系统(六)「命名空间(不推荐使用)」

在TypeScript的编程世界里,命名空间(Namespace)是一种用于组织相关代码的有效方式。它就像是一个容器🧳,把相关的变量、函数、类和接口等代码元素整合在一起,避免命名冲突,提高代码的可维护性和可读性。不过,需要注意的是,随着ES模块的出现,官方已经不推荐使用命名空间了,但了解它仍然有助于我们更好地理解TypeScript的发展历程和代码结构。

命名空间的基本用法📦

命名空间的主要作用是创建一个封闭的作用域,在这个作用域内定义的变量和函数等,默认情况下只能在该命名空间内部使用。我们来看下面这个例子:

namespace Utils {
  function isString(value:any) {
    return typeof value === 'string';
  }
  // 正确,在命名空间内部调用
  isString('yes');
}
// 报错,在命名空间外部调用
Utils.isString('no'); 

在上面的代码中,Utils命名空间里定义了isString函数,它在Utils内部可以正常使用,但如果在外部直接调用就会报错。

要是想在命名空间之外使用其内部成员,就得给该成员加上export前缀,这样它就变成了对外输出的接口。比如:

namespace Utility {
  export function log(msg:string) {
    console.log(msg);
  }
  export function error(msg:string) {
    console.error(msg);
  }
}
Utility.log('Call me');
Utility.error('maybe!');

加上export前缀后,logerror函数就能在Utility命名空间外部被调用了。编译后的JavaScript代码如下:

var Utility;
(function (Utility) {
  function log(msg) {
    console.log(msg);
  }
  Utility.log = log;
  function error(msg) {
    console.error(msg);
  }
  Utility.error = error;
})(Utility || (Utility = {}));

从编译后的代码可以看出,命名空间Utility变成了JavaScript中的一个对象,使用export的内部成员都成了这个对象的属性。这意味着命名空间不仅仅是类型代码,它的值会保留在编译后的代码中,使用时需要特别注意。

在命名空间内部,还可以使用import命令引入外部成员,并为其起别名。这在外部成员名称较长时,能有效简化代码。例如:

namespace Utils {
  export function isString(value:any) {
    return typeof value === 'string';
  }
}
namespace App {
  import isString = Utils.isString;
  isString('yes');
  // 等同于
  Utils.isString('yes');
}

这里,import命令在App命名空间内,为Utils.isString函数指定了别名isString,使用起来更加方便。import命令也能在命名空间外部使用,为命名空间或其成员指定别名:

namespace Shapes {
  export namespace Polygons {
    export class Triangle {}
    export class Square {}
  }
}
import polygons = Shapes.Polygons;
// 等同于 new Shapes.Polygons.Square()
let sq = new polygons.Square();

在这个例子中,import命令在Shapes命名空间外部,将Shapes.Polygons的别名指定为polygons

命名空间还支持嵌套使用,就像俄罗斯套娃一样🎁。例如:

namespace Utils {
  export namespace Messaging {
    export function log(msg:string) {
      console.log(msg);
    }
  }
}
Utils.Messaging.log('hello') // "hello"

在这个例子中,Utils命名空间内部嵌套了Messaging命名空间。如果要在外部使用Messaging,必须加上export命令,而且引用时要从最外层开始,如Utils.Messaging.log()

命名空间不仅能包含实际的代码逻辑,还能包含类型代码,比如接口和类:

namespace N {
  export interface MyInterface{}
  export class MyClass{}
}

这里,N命名空间对外输出了接口MyInterface和类MyClass,它们都可以作为类型使用。

虽然命名空间和模块都能组织相关代码并对外输出接口,但它们还是有区别的。一个文件中只能有一个模块,但可以有多个命名空间。由于模块是JavaScript的标准语法,并且不需要编译转换,所以更推荐使用模块来替代命名空间。

要是命名空间代码写在单独的文件里,引入这个文件时需要使用三斜杠语法:

/// <reference path = "SomeFileName.ts" />

命名空间的输出📤

命名空间本身也可以通过export命令输出,供其他文件使用。例如,在shapes.ts文件中:

// shapes.ts
export namespace Shapes {
  export class Triangle {
    // ...
  }
  export class Square {
    // ...
  }
}

在其他脚本文件中,可以使用import命令加载这个命名空间:

// 写法一
import { Shapes } from './shapes';
let t = new Shapes.Triangle();
// 写法二
import * as shapes from "./shapes";
let t = new shapes.Shapes.Triangle();

不过,还是建议优先使用模块的输出和输入方式,这样代码更简洁、规范:

// shapes.ts
export class Triangle {
  /* ... */
}
export class Square {
  /* ... */
}
// shapeConsumer.ts
import * as shapes from "./shapes";
let t = new shapes.Triangle();

命名空间的合并📏

TypeScript中,多个同名的命名空间会自动合并,这和接口的合并类似。比如:

namespace Animals {
  export class Cat {}
}
namespace Animals {
  export interface Legged {
    numberOfLegs: number;
  }
  export class Dog {}
}
// 等同于
namespace Animals {
  export interface Legged {
    numberOfLegs: number;
  }
  export class Cat {}
  export class Dog {}
}

这种合并机制非常实用,如果同名的命名空间分布在不同文件中,TypeScript会将它们合并在一起,方便我们扩展代码。但要注意,合并命名空间时,非export的成员不会被合并,而且只能在各自的命名空间中使用:

namespace N {
  const a = 0;
  export function foo() {
    console.log(a);  // 正确
  }
}
namespace N {
  export function bar() {
    foo(); // 正确
    console.log(a);  // 报错
  }
}

在这个例子中,变量a是第一个N命名空间的非对外成员,只能在第一个命名空间内使用。

命名空间还能和同名函数、类、枚举合并,但要求同名函数、类、枚举必须在命名空间之前声明。这是为了先创建函数、类或枚举对象,然后同名命名空间就可以为它们添加额外的属性或方法。

function f() {
  return f.version;
}
namespace f {
  export const version = '1.0';
}
f()   // '1.0'
f.version // '1.0'

这里,函数f和命名空间f合并,命名空间为函数对象f添加了version属性。

class C {
  foo = 1;
}
namespace C {
  export const bar = 2;
}
C.bar // 2

在这个例子中,命名空间C为类C添加了静态属性bar

enum E {
  A,
  B,
  C,
}
namespace E {
  export function foo() {
    console.log(E.C);
  }
}
E.foo() // 2

命名空间E为枚举E添加了foo方法。但要注意,枚举成员和命名空间导出成员不允许同名,否则会报错:

enum E {
  A, // 报错
  B,
}
namespace E {
  export function A() {} // 报错
}

通过以上对TypeScript命名空间的深入了解,我们掌握了它的基本用法、输出方式和合并规则。虽然现在官方更推荐使用模块,但命名空间在一些特定场景或旧代码库中仍然有其价值。希望大家在编程过程中,根据实际情况灵活选择合适的代码组织方式,编写出更优秀的代码💻。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值