Symbol 就像一个箱子,哪怕里面装的东西是一样的,也是没法相等的,这是因为每一个箱子都是唯一的。

Symbol 产生主要跟for of循环有关,而for offor in的区别就是,in 遍历的是对象的key, 而 of 则是遍历 value

只要实现了 Symbol.iterator 这个接口,就可以通过for of遍历。

最简单的例子就是

  1. let list = [4, 5, 6];
  2. for (let i in list) {
  3. console.log(i); // "0", "1", "2",
  4. }
  5. for (let i of list) {
  6. console.log(i); // "4", "5", "6"
  7. }

模块导入与导出

我们知道,基本上任何语言的都有相应管理代码导包的机制,比如 java 的 package,php 的 include 和 require 等。

而我们的 js 当可以运行在后端的时候,所以必须要有一种模块加载机制,于是就有了 cjs 规范。

而对于前端浏览器环境来说,我们引入 script 是通过标签进行引入的,而我们加载好的库,通常会在全局变量上面挂载某一个变量,比如我们熟知的$,然而就是应该这样的方式容易导致模块的命名冲突,假如 a.jsb.js 都想往 window 上面挂载一个 $对象,这样我们就没法确定这个$从哪来的。

所以说我们不得不重视模块的重要性。

ts 中,我们想要让别人使用的方法就export导出。想要使用别人的方法就用import导入。

创建我们的 a.ts 文件。

  1. export function whatsYourName(name: string) {
  2. console.log(name);
  3. }
  4. export interface StringValidator {
  5. isAcceptable(s: string): boolean;
  6. }
  7. export const numberRegexp = /^[0-9]+$/;
  8. class ZipCodeValidator implements StringValidator {
  9. isAcceptable(s: string) {
  10. return s.length === 5 && numberRegexp.test(s);
  11. }
  12. }
  13. export { ZipCodeValidator };
  14. export { ZipCodeValidator as mainValidator };

其实 ts 跟 es6 的导出基本是一致的。

假如我们像上面这样导出,我们新建一个 b.ts 来导入 a 导出的东西。

Symbol 与模块 - 图1

这里就提示了我们所有导出的方法。我们可以看到我用了一个{},其实这就是解构。

我们可以把导出了看做为一个对象{}export function whatsYourName,其实就是在导出的对象上面挂载一个whatsYourName引用。

当然我们的export可以直接写在声明的前面,或者使用先声明后导出的模式。

export { ZipCodeValidator }; 这种先声明后导出,都需要用 {} 包裹起来。

ZipCodeValidator as mainValidator 这里的 as 并不是类型转换,而且给导出的取一个别名。

  1. export default {
  2. name: 'a'
  3. }

当我们使用这样的 default 关键字的好处就是,我并不需要具体知道你类里面有哪些方法,我直接拿,就是拿到的一个默认导出, 也就是default 后面的东西,导入的时候不用加{}

export 可以多次导出,不过取的时候要用{}和它具体的名称,因为只有对应了才能解构。

export default则不需要{},而且你可以给它取任何名称,并且一个模块只能有一个默认导出。

  1. import a from "./a";

比如这里拿到的 a就是{name:'a'}

Symbol 与模块 - 图2

当然我们也可以使用* as 这样的语法,把 a 模块里面所有 export 导出的都挂载到 all 这个变量上面去。

此时我们再修改一下我们b.ts

  1. export * from './a';
  2. export const name = 'b.ts';

export * from './a'; 就是把 a 文件里面所有 export 的,再导出一次。

再次新建一个c.ts 文件

同样可以得到提示。

Symbol 与模块 - 图3

这样是不包含默认导出的。

Symbol 与模块 - 图4

假如想导出 a 的默认导出。

  1. import a from './a';
  2. export { a };

我们只能这样导出,没人任何其他的简写形式。

我们通过命令

  1. tsc --module commonjs a.ts

编译得到commonjs规范的 js 文件

  1. exports.mainValidator = ZipCodeValidator;
  2. exports.__esModule = true;
  3. exports["default"] = {
  4. name: 'a'
  5. };

多有的 export 导出的变成了exports上面的属性。而默认导出的挂载到了default属性上面。

  1. tsc --module amd a.ts

而 amd

  1. define(["require", "exports"], function (require, exports) {
  2. "use strict";
  3. function whatsYourName(name) {
  4. console.log(name);
  5. }
  6. exports.whatsYourName = whatsYourName;
  7. exports.numberRegexp = /^[0-9]+$/;
  8. var ZipCodeValidator = (function () {
  9. function ZipCodeValidator() {
  10. }
  11. ZipCodeValidator.prototype.isAcceptable = function (s) {
  12. return s.length === 5 && exports.numberRegexp.test(s);
  13. };
  14. return ZipCodeValidator;
  15. }());
  16. exports.ZipCodeValidator = ZipCodeValidator;
  17. exports.mainValidator = ZipCodeValidator;
  18. exports.__esModule = true;
  19. exports["default"] = {
  20. name: 'a'
  21. };
  22. });

就是在外面加了一个大的 define 函数而已,这个函数来自 require.js,前端的一个异步加载 js 解决方案。

假如你通过node运行会报错,因为此时没有 define 函数

以及umd,这是一种兼容 amdcommonjs 的做法。通过node依然可以运行。

不信你可以自己打印点东西看下。

  1. tsc --module amd a.ts
  1. (function (dependencies, factory) {
  2. if (typeof module === 'object' && typeof module.exports === 'object') {
  3. var v = factory(require, exports); if (v !== undefined) module.exports = v;
  4. }
  5. else if (typeof define === 'function' && define.amd) {
  6. define(dependencies, factory);
  7. }
  8. })(["require", "exports"], function (require, exports) {
  9. "use strict";
  10. function whatsYourName(name) {
  11. console.log(name);
  12. }
  13. exports.whatsYourName = whatsYourName;
  14. exports.numberRegexp = /^[0-9]+$/;
  15. var ZipCodeValidator = (function () {
  16. function ZipCodeValidator() {
  17. }
  18. ZipCodeValidator.prototype.isAcceptable = function (s) {
  19. return s.length === 5 && exports.numberRegexp.test(s);
  20. };
  21. return ZipCodeValidator;
  22. }());
  23. exports.ZipCodeValidator = ZipCodeValidator;
  24. exports.mainValidator = ZipCodeValidator;
  25. exports.__esModule = true;
  26. exports["default"] = {
  27. name: 'a'
  28. };
  29. });

还有 System

  1. tsc --module system a.ts
  1. System.register([], function (exports_1, context_1) {
  2. "use strict";
  3. var __moduleName = context_1 && context_1.id;
  4. function whatsYourName(name) {
  5. console.log(name);
  6. }
  7. exports_1("whatsYourName", whatsYourName);
  8. var numberRegexp, ZipCodeValidator;
  9. return {
  10. setters: [],
  11. execute: function () {
  12. exports_1("numberRegexp", numberRegexp = /^[0-9]+$/);
  13. ZipCodeValidator = (function () {
  14. function ZipCodeValidator() {
  15. }
  16. ZipCodeValidator.prototype.isAcceptable = function (s) {
  17. return s.length === 5 && numberRegexp.test(s);
  18. };
  19. return ZipCodeValidator;
  20. }());
  21. exports_1("ZipCodeValidator", ZipCodeValidator);
  22. exports_1("mainValidator", ZipCodeValidator);
  23. exports_1("default", {
  24. name: 'a'
  25. });
  26. }
  27. };
  28. });

而这个 System 的导出是通过exports_1("ZipCodeValidator", ZipCodeValidator);导出的。

第一个参数是导出名称,第二个参数是导出的引用。

现在,你应该都清楚,每一种模式转换之后的 js都是如何导出的了。

其实这些导出,都是挂载到某一个变量下面,集中管理,或许是对象,或许是数组,等需要用的时候,再去根据名字拿就是了。

外部模块与内部模块

外部模块,顾名思义,外,表示不属于内部的,对于 ts 语言来说,内部就是 ts 文件,此时的外代表着 js 文件。

我们知道引用 JS 文件,需要为它写 d.ts 文件,此时拥有d.ts的文件,我们可以把它看做js + d.ts = .ts

而引用 js 或者 ts 文件需要 import,我们把所有需要import的都叫做引用外部模块。因为模块是基于文件的导入导出的,需要导入的就是来自外部的。

内部模块就代表着 ts 内部的,同时它有一个别名叫做命名空间。命名空间的作用就是把一份代码分割到多个文件。

模块的寻找

其实跟 node 寻找模块一样,这里简单的说一下。

它会根据你写的相对路径去找文件。ts 因为需要代码提示,所以它通常会找d.ts.ts 文件。

假如你写的是绝对路径,比如import $ from 'jquery'这种,我们知道 jquery 并没有.ts版本的,但是有人提供了jqueryd.ts文件。

在 ts2.0 版本之后,我们直接直接通过npm install @types/xxx安装d.ts文件。

当我们通过 npm install @types/jquery 安装之后,会在我们的node_modules里面有个@types文件夹,里面存放着我们的d.ts文件。

ts 找不到的时候会来这个目录找,再找不到就报错了,当然你可以定一些它需要寻找的特定目录,比如说你创建一个专门存放d.ts的文件夹,然后在tsconfig.json配置一下。

tsconfig.json 就是我们编译ts文件的编译选项。

小实验

首先创建一个文件夹ts-modules

通过tsc --init 生成 tsconfig.json

通过npm init -y 生成 package.json

再安装jquery

  1. mkdir ts-modules
  2. cd ts-modules
  3. tsc --init
  4. npm init -y
  5. npm install jquery @types/jquery -S

创建 src 目录,进入再创建我们的main.ts

  1. mkdir src
  2. cd src
  3. touch main.ts

你一定要清楚 @types/jqueryd.tsjqueryjs,只有这俩样合起来才能被正常使用。

还有一点你必须要明白,tsc 并不会打包代码。

在你的 main.ts 里面输入

  1. import * as $ from "jquery";
  2. $(document).html("Hello World!");

来到tsconfig.json,添加一行。

  1. "outDir": "./dist"

在你的终端里面

  1. tsc

我们可以看到dist/main.js

  1. "use strict";
  2. var $ = require("jquery");
  3. $(document).html("Hello World!");

此时的 jquery 并没有打包到该文件里面。

此时新建我们的modules.tsnamespace.ts

  1. // modules.ts
  2. export const modules_a = 123;
  1. // namespace.ts
  2. namespace OwnSpace{
  3. export let var_a = 'own_space';
  4. }

Symbol 与模块 - 图5

当我们在main.ts里面输入 OwnSpace的时候,我们发现出现了代码提示。

modules.ts 里面导出的modules_a则不会,这是模块与命名空间的一个小区别。

当我们给namespace.ts添加一个导出的时候,立刻就报错了。

Symbol 与模块 - 图6

Symbol 与模块 - 图7

所有我们知道,包含 namespace 的外层不应该有 export,要不然就变成模块了。

当然namespace 里面的变量只有导出才能被访问。

tsc 编译一下

来看看我们编译出来的命名空间

  1. var OwnSpace;
  2. (function (OwnSpace) {
  3. OwnSpace.var_a = 'own_space';
  4. })(OwnSpace || (OwnSpace = {}));

其实他就是自执行函数,然后给他挂载一些变量而已。

Symbol 与模块 - 图8

其实就跟这里演示的一样,通过var其实就是挂载到了window变量上面。

而多次通过var声明并不会报错。

修改一下namespace.ts

  1. namespace OwnSpace{
  2. export let var_a = 'own_space_b';
  3. }
  4. namespace OwnSpace{
  5. export let var_b = 'own_space_b';
  6. let inner_b = '123';
  7. }

编译出来的会像这样。

  1. var OwnSpace;
  2. (function (OwnSpace) {
  3. OwnSpace.var_a = 'own_space_b';
  4. })(OwnSpace || (OwnSpace = {}));
  5. (function (OwnSpace) {
  6. OwnSpace.var_b = 'own_space_b';
  7. var inner_b = '123';
  8. })(OwnSpace || (OwnSpace = {}));

只有 export 出来的变量才会挂载到OwnSpace变量上面。

此时我们再新建一个name2.ts

  1. namespace OwnSpace{
  2. export let var_c = 'own_space_c';
  3. }

编译之后,在 dist目录新建一个 index.html

  1. <meta charset="utf-8">
  2. <script src='namespace.js'></script>
  3. <script src='name2.js'></script>
  4. <script>
  5. console.log(OwnSpace);
  6. </script>

用浏览器打开它,并打开控制台

Symbol 与模块 - 图9

namespace.js 挂载了var_avar_bname2.js 挂载了 var_c

我们看到所有的命名空间下面的东西都是通过.来访问的,我们可以认为命名空间就是一个对象。

所以 namespace 有什么作用呢?

其实非常明显他就是往 window 上面挂载某些变量,就像 jquery

当你在 html 中引入<script src='xxxxxxx/jquery.js'></script> 它就会往window 上面挂载一个$

其实这样非常鸡肋,容易造成全局命名空间污染,而且现在都有打包工具了,没必要这么干,之所以会有 namespace 可能是为了方便为已有的js库写d.ts

所以大多数时候你是用不到 namespace 的。

配置 d.ts

typescript 的代码核心就是d.ts,所以说只要d.ts写的好,走遍天下都不怕。

继续小实验

在之前的小实验里面,再新建俩个文件,some.d.tssome.js

  1. // some.d.ts
  2. declare const name : string;
  3. export default name;
  1. // some.js
  2. export defalt const name = 'hello world';

main.ts 文件里面

  1. import name from './some';

这里我们使用的是相对路径。这样是正确无误的。

创建test.d.ts,定义一个外部模块。外部模块是不是需要 import 的啊?

我们发现这里有一个双引号

  1. declare module "lodash"{
  2. export let version : string;
  3. let _ : any;
  4. export default _;
  5. }

main.ts

  1. import { version } from "lodash";

这里的双引号就和之前的对应,你会发现这里没有相对路径,而是直接 "lodash"

这里之所以能找到是因为它们在同一个目录。

而这寻找,他会遍历你项目里面的文件,你不信可以把这个文件移动到项目根目录下。

其实编译器会在 main.ts文件的头部会自动添加一个这样的编译指令,尽管现在你是看不到它的。

这个指令会告诉编译器去哪寻找d.ts文件,表示这个文件使用了d.ts里面声明的名字; 并且,这个包要在编译阶段与声明文件一起被包含进来

  1. /// <reference path="../test.d.ts" />

在你的 tscofig.json 里面配置这一项。

  1. "listFiles": true

Symbol 与模块 - 图10

而且还可以配置去哪找,分别是 typeRootstypes 选项。

所有的选项都在这

  1. https://www.tslang.cn/docs/handbook/compiler-options.html

而关于编译器怎么遍历.d.ts目录。

在你的 tscofig.json 里面配置这一项。

  1. "traceResolution": true

再次编译你就可以看到打印出了寻找路径。

Symbol 与模块 - 图11

里面还有非常多的配置选项,自己去尝试一下,算是留给大家的课后作业。

学习的最好办法就是尝试。