思考 TypeScript namespace,复习 class 语法

发布时间 2023-04-06 15:08:48作者: Himmelbleu

前言

据我所知,早期 JavaScript 没有 class 语法,很多都是函数,即便是现在的 class 本质上也是一个函数。在这里不说函数与 class 之间的关系和区别。

下面将从 class 语法上讲解,阐述为什么有 class 以及作用;

对 class 语法进行了探讨之后,再思考 TypeScript 的 namespace 给我们带来了什么好处。

class 语法

class 可以很好的组织一段代码,封装变量、函数。class 能保留在此运行阶段的变量变化(记忆功能),并且 class 也可以解决变量在全局作用域下的冲突问题。

class Person {
  constructor(name) {
    this._name = name;
    this._count = 0;
  }

  set name(value) {
    this._name = value;
  }

  get name() {
    return this._name;
  }

  get count() {
    return this._count;
  }

  say(msg) {
    console.log(this._name + "say, " + msg);
    this._count++;
  }
}

const p = new Person("小明");
p.say("hello");
p.say("world");
p.say("!");

console.log(p.count); // 3

类里面的 count 变量被记录,类可以封装一段代码,保留这段代码的记忆。类与类之间的变量、函数等都不会因为重复命名而冲突。

类就是模块化、整体开发的一种体现

模块化

与上面 class 相反,一些不需要保存变量状态,很多函数之间没有强联系(不适合组织在一个 class 里)时,尝试通过 .js 文件(脚本文件)分割代码:

// a.js
function getDate() { return new Date(); }
// b.js
function getDate() { return new Date(); }

引入到同一个 html 下,它们的顶层作用域都是一个。因此,a 与 b 两个脚本文件中定义的相同名称的函数会引起变量命重复定义的冲突。

<script src="./a.js"></script>
<script src="./b.js"></script>

多个脚本文件引入到 html 文件时有变量冲突问题,此问题迫切需要解决。

在现代 JavaScript 中,已经有了模块化开发的语法支持,importexport

// a.js
function getDate() { return new Date(); }
// b.js
function getDate() { return new Date(); }

同一个文件引入同一个函数时只需要在导入端重命名即可,不影响源文件中定义的函数名称,对代码入侵性更小

// main.js
import { getDate as aGetDate } from "./a.js";
import { getDate as bGetDate } from "./b.js";

可以简单地把一个脚本文件视为一个模块。模块可以更方便地组织无状态、相关性不强的代码,提升项目工程的管理能力。

namespace

TypeScript namespace 以前叫内部模块(internal modules)。namespace 扩展和补充模块。随着业务的增加,代码越来越多,在一个脚本文件中定义的代码,需再新建一个脚本文件写新的代码。从逻辑上来讲应该还是定义在同一个脚本文件中。

namespace 可以对模块再拆分,这样即可以不新建脚本文件、减少具名 import,还能解决变量名重复的问题。

// validator.ts

namespace Validation {
  // 导出该接口,通过 Validation.StringValidator 访问
  export interface StringValidator {
    isAcceptable(s: string): boolean;
  }

  // Validation 命名空间私有的变量
  const lettersRegexp = /^[A-Za-z]+$/;
  const numberRegexp = /^[0-9]+$/;

  export class LettersOnlyValidator implements StringValidator {
    isAcceptable(s: string) {
      return lettersRegexp.test(s);
    }
  }

  export class ZipCodeValidator implements StringValidator {
    isAcceptable(s: string) {
      return s.length === 5 && numberRegexp.test(s);
    }
  }
}

总结

namespaceimport/export 都是模块,组织的是一堆函数、类、变量,一些无状态的,变量导出之后,状态变化不会反应到定义的文件中,而是使用的文件里,这些通常是常数,而不是 letvar 定义的变量。

class 比上面两个都小,同样也是组织函数、变量。但这些变量能保存在类的内部,并形成记忆,是有状态的。

namespace 是对 import/export 的补充和扩展,一个模块内的代码太多,不好组织,可以通过 namespace 来进行组织。