Symbol 与模块
Symbol 就像一个箱子,哪怕里面装的东西是一样的,也是没法相等的,这是因为每一个箱子都是唯一的。
Symbol 产生主要跟for of
循环有关,而for of
与for in
的区别就是,in
遍历的是对象的key
, 而 of
则是遍历 value
。
只要实现了 Symbol.iterator
这个接口,就可以通过for of
遍历。
最简单的例子就是
模块导入与导出
我们知道,基本上任何语言的都有相应管理代码导包的机制,不如 java 的 package,php 的 include 和 require 等。
而我们的 js 当可以运行在后端的时候,所以必须要有一种模块加载机制,于是就有了 cjs
规范。
而对于前端浏览器环境来说,我们引入 script 是通过标签进行引入的,而我们加载好的库,通常会在全局变量上面挂载某一个变量,比如我们熟知的$
,然而就是应该这样的方式容易导致模块的命名冲突,假如 a.js
和 b.js
都想往 window
上面挂载一个 $
对象,这样我们就没法确定这个$
从哪来的。
所以说我们不得不重视模块的重要性。
在 ts
中,我们想要让别人使用的方法就export
导出。想要使用别人的方法就用import
导入。
创建我们的 a.ts
文件。
其实 ts 跟 es6 的导出基本是一致的。
假如我们像上面这样导出,我们新建一个 b.ts
来导入 a 导出的东西。
这里就提示了我们所有导出的方法。我们可以看到我用了一个{}
,其实这就是解构。
我们可以把导出了看做为一个对象{}
,export function whatsYourName
,其实就是在导出的对象上面挂载一个whatsYourName
引用。
当然我们的export
可以直接写在声明的前面,或者使用先声明后导出的模式。
export { ZipCodeValidator };
这种先声明后导出,都需要用 {}
包裹起来。
ZipCodeValidator as mainValidator
这里的 as 并不是类型转换,而且给导出的取一个别名。
当我们使用这样的 default
关键字的好处就是,我并不需要具体知道你类里面有哪些方法,我直接拿,就是拿到的一个默认导出, 也就是default
后面的东西,导入的时候不用加{}
。
export
可以多次导出,不过取的时候要用{}
和它具体的名称,因为只有对应了才能解构。
而export default
则不需要{}
,而且你可以给它取任何名称,并且一个模块只能有一个默认导出。
比如这里拿到的 a
就是{name:'a'}
当然我们也可以使用* as
这样的语法,把 a
模块里面所有 export 导出的都挂载到 all 这个变量上面去。
此时我们再修改一下我们b.ts
export * from './a';
就是把 a 文件里面所有 export 的,再导出一次。
再次新建一个c.ts
文件
同样可以得到提示。
这样是不包含默认导出的。
假如想导出 a 的默认导出。
我们只能这样导出,没人任何其他的简写形式。
我们通过命令
编译得到commonjs
规范的 js 文件
多有的 export
导出的变成了exports
上面的属性。而默认导出的挂载到了default
属性上面。
而 amd
就是在外面加了一个大的 define
函数而已,这个函数来自 require.js
,前端的一个异步加载 js 解决方案。
假如你通过node
运行会报错,因为此时没有 define
函数
以及umd
,这是一种兼容 amd
与 commonjs
的做法。通过node
依然可以运行。
不信你可以自己打印点东西看下。
还有 System
而这个 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
版本的,但是有人提供了jquery
的 d.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
创建 src
目录,进入再创建我们的main.ts
你一定要清楚 @types/jquery
是 d.ts
而jquery
是 js
,只有这俩样合起来才能被正常使用。
还有一点你必须要明白,tsc
并不会打包代码。
在你的 main.ts
里面输入
来到tsconfig.json
,添加一行。
在你的终端里面
我们可以看到dist/main.js
此时的 jquery 并没有打包到该文件里面。
此时新建我们的modules.ts
和 namespace.ts
当我们在main.ts
里面输入 OwnSpace
的时候,我们发现出现了代码提示。
而modules.ts
里面导出的modules_a
则不会,这是模块与命名空间的一个小区别。
当我们给namespace.ts
添加一个导出的时候,立刻就报错了。
所有我们知道,包含 namespace
的外层不应该有 export
,要不然就变成模块了。
当然namespace
里面的变量只有导出才能被访问。
tsc
编译一下
来看看我们编译出来的命名空间
其实他就是自执行函数,然后给他挂载一些变量而已。
其实就跟这里演示的一样,通过var
其实就是挂载到了window
变量上面。
而多次通过var
声明并不会报错。
修改一下namespace.ts
编译出来的会像这样。
只有 export
出来的变量才会挂载到OwnSpace
变量上面。
此时我们再新建一个name2.ts
编译之后,在 dist
目录新建一个 index.html
用浏览器打开它,并打开控制台
namespace.js
挂载了var_a
和 var_b
name2.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.ts
、some.js
在main.ts
文件里面
这里我们使用的是相对路径。这样是正确无误的。
创建test.d.ts
,定义一个外部模块。外部模块是不是需要 import 的啊?
我们发现这里有一个双引号
在 main.ts
中
这里的双引号就和之前的对应,你会发现这里没有相对路径,而是直接 "lodash"
。
这里之所以能找到是因为它们在同一个目录。
而这寻找,他会遍历你项目里面的文件,你不信可以把这个文件移动到项目根目录下。
其实编译器会在 main.ts
文件的头部会自动添加一个这样的编译指令,尽管现在你是看不到它的。
这个指令会告诉编译器去哪寻找d.ts
文件,表示这个文件使用了d.ts
里面声明的名字; 并且,这个包要在编译阶段与声明文件一起被包含进来
在你的 tscofig.json
里面配置这一项。
而且还可以配置去哪找,分别是 typeRoots
和 types
选项。
所有的选项都在这
而关于编译器怎么遍历.d.ts
目录。
在你的 tscofig.json
里面配置这一项。
再次编译你就可以看到打印出了寻找路径。
里面还有非常多的配置选项,自己去尝试一下,算是留给大家的课后作业。
学习的最好办法就是尝试。
Last updated