[
  {
    "path": ".editorconfig",
    "content": "# EditorConfig is awesome: https://EditorConfig.org\n\n# top-most EditorConfig file\nroot = true\n\n# Unix-style newlines with a newline ending every file\n[*]\nend_of_line = lf\ninsert_final_newline = true\n\n# Matches multiple files with brace expansion notation\n# Set default charset\n[*]\ncharset = utf-8\n\n# 4 space indentation\n# [*]\n# indent_style = space\n# indent_size = 4\n"
  },
  {
    "path": ".eslintignore",
    "content": "*.d.ts\n"
  },
  {
    "path": ".eslintrc.js",
    "content": "module.exports = {\n  extends: ['alloy', 'alloy/react', 'alloy/typescript'],\n  env: {\n    // Your environments (which contains several predefined global variables)\n    //\n    // browser: true,\n    // mocha: true,\n    // jquery: true\n  },\n  globals: {\n    // Your global variables (setting to false means it's not allowed to be reassigned)\n    //\n    // myGlobal: false\n  },\n  rules: {\n    // Customize your rules\n    'no-undef': 'off',\n    'prefer-arrow-callback': 'off',\n    '@typescript-eslint/no-invalid-this': 'off',\n    '@typescript-eslint/no-require-imports': 'off',\n    '@typescript-eslint/method-signature-style': 'off',\n  },\n};\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: xcatliu\npatreon: # Replace with a single Patreon username\nopen_collective: # Replace with a single Open Collective username\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\notechie: # Replace with a single Otechie username\ncustom: https://github.com/xcatliu/buy-me-a-coffee\n"
  },
  {
    "path": ".github/workflows/gh-pages.yml",
    "content": "name: gh-pages\n\non:\n  push:\n    branches:\n      - master\n\njobs:\n  build-and-deploy:\n    runs-on: ubuntu-22.04\n    steps:\n      - uses: actions/checkout@v2\n        with:\n          fetch-depth: 0\n\n      - name: Setup deno\n        uses: denolib/setup-deno@v2\n        with:\n          deno-version: v1.34.1\n\n      - name: Build gh-pages\n        run: |\n          curl -fsSL https://deno.land/x/install/install.sh | sh\n          export DENO_INSTALL=\"/home/runner/.deno\"\n          export PATH=\"$DENO_INSTALL/bin:$PATH\"\n          deno --version\n          deno install --unstable --allow-read --allow-write --allow-net --allow-run -n pagic https://deno.land/x/pagic@v1.6.3/mod.ts\n          pagic build\n\n      - name: Deploy gh-pages\n        uses: peaceiris/actions-gh-pages@v3\n        with:\n          github_token: ${{ secrets.GITHUB_TOKEN }}\n          publish_dir: ./dist\n          cname: ts.xcatliu.com\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\nnode_modules\ndist\n"
  },
  {
    "path": ".lintmdrc",
    "content": "{\n  \"excludeFiles\": [],\n  \"rules\": {\n    \"no-trailing-punctuation\": 0,\n    \"no-long-code\": 0\n  }\n}\n"
  },
  {
    "path": ".npmrc",
    "content": "registry=https://registry.npmjs.org/\n"
  },
  {
    "path": ".prettierignore",
    "content": "*.md\n*.png\n*.epub\n\nnode_modules\n\nassets\n\n.DS_Store\n.editorconfig\n.eslintignore\n.gitignore\n.lintmdrc\n.prettierignore\n.npmrc\n\npnpm-lock.yaml\n\nexamples\ndist\n"
  },
  {
    "path": ".prettierrc.js",
    "content": "// .prettierrc.js\nmodule.exports = {\n  // 一行最多 120 字符\n  printWidth: 120,\n  // 使用 2 个空格缩进\n  tabWidth: 2,\n  // 不使用缩进符，而使用空格\n  useTabs: false,\n  // 行尾需要有分号\n  semi: true,\n  // 使用单引号\n  singleQuote: true,\n  // 对象的 key 仅在必要时用引号\n  quoteProps: 'as-needed',\n  // jsx 不使用单引号，而使用双引号\n  jsxSingleQuote: false,\n  // 末尾需要有逗号\n  trailingComma: 'all',\n  // 大括号内的首尾需要空格\n  bracketSpacing: true,\n  // jsx 标签的反尖括号需要换行\n  bracketSameLine: false,\n  // 箭头函数，只有一个参数的时候，也需要括号\n  arrowParens: 'always',\n  // 每个文件格式化的范围是文件的全部内容\n  rangeStart: 0,\n  rangeEnd: Infinity,\n  // 不需要写文件开头的 @prettier\n  requirePragma: false,\n  // 不需要自动在文件开头插入 @prettier\n  insertPragma: false,\n  // 使用默认的折行标准\n  proseWrap: 'preserve',\n  // 根据显示样式决定 html 要不要折行\n  htmlWhitespaceSensitivity: 'css',\n  // vue 文件中的 script 和 style 内不用缩进\n  vueIndentScriptAndStyle: false,\n  // 换行符使用 lf\n  endOfLine: 'lf',\n  // 格式化嵌入的内容\n  embeddedLanguageFormatting: 'auto',\n  // html, vue, jsx 中每个属性占一行\n  singleAttributePerLine: false,\n};\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"files.eol\": \"\\n\",\n  \"editor.tabSize\": 2,\n  \"editor.formatOnSave\": true,\n  \"editor.defaultFormatter\": \"esbenp.prettier-vscode\",\n  \"eslint.validate\": [\"javascript\", \"javascriptreact\", \"typescript\", \"typescriptreact\"],\n  \"editor.codeActionsOnSave\": {\n    // 保存时自动修复 ESLint 错误\n    \"source.fixAll.eslint\": true\n  },\n  \"typescript.tsdk\": \"node_modules/typescript/lib\",\n  \"[json]\": {\n    \"editor.defaultFormatter\": \"esbenp.prettier-vscode\"\n  },\n  \"[jsonc]\": {\n    \"editor.defaultFormatter\": \"esbenp.prettier-vscode\"\n  }\n}\n"
  },
  {
    "path": "README.md",
    "content": "---\nnext: introduction/README.md\n---\n\n# TypeScript 入门教程\n\n[![Actions Status](https://github.com/xcatliu/typescript-tutorial/workflows/gh-pages/badge.svg)](https://github.com/xcatliu/typescript-tutorial/actions)\n\n从 JavaScript 程序员的角度总结思考，循序渐进的理解 TypeScript。\n\n## 关于本书\n\n- [在线阅读](https://ts.xcatliu.com/)\n- [GitHub 地址][GitHub]\n- 作者：[xcatliu](https://github.com/xcatliu/)\n- 本网站使用 [Pagic](https://github.com/xcatliu/pagic) 构建\n\n本书是作者在学习 [TypeScript] 后整理的学习笔记。\n\n随着对 TypeScript 理解的加深和 TypeScript 社区的发展，本书也会做出相应的更新，欢迎大家 [Star 收藏][GitHub]。\n\n- 发现文章内容有问题，可以直接在页面下方评论\n- 对项目的建议，可以[提交 issue](https://github.com/xcatliu/typescript-tutorial/issues/new) 向作者反馈\n- 欢迎直接提交 pull-request 参与贡献\n\n## 为什么要写本书\n\nTypeScript 虽然有[官方手册][Handbook]及其[非官方中文版][中文手册]，但是它每一章都希望能详尽的描述一个概念，导致前面的章节就会包含很多后面才会学习到的内容，而有些本该一开始就了解的基础知识却在后面才会涉及。如果是初学者，可能需要阅读多次才能理解。所以它更适合用来查阅，而不是学习。\n\n与官方手册不同，本书着重于从 JavaScript 程序员的角度总结思考，循序渐进的理解 TypeScript，希望能给大家一些帮助和启示。\n\n由于一些知识点与官方手册重合度很高，本书会在相应章节推荐直接阅读中文手册。\n\n## 关于 TypeScript\n\n[TypeScript] 是 JavaScript 的一个超集，主要提供了**类型系统**和**对 ES6 的支持**，它由 Microsoft 开发，代码[开源于 GitHub](https://github.com/Microsoft/TypeScript) 上。\n\n它的第一个版本发布于 2012 年 10 月，经历了多次更新后，现在已成为前端社区中不可忽视的力量，不仅在 Microsoft 内部得到广泛运用，而且 Google 开发的 [Angular](https://angular.io/) 从 2.0 开始就使用了 TypeScript 作为开发语言，[Vue](https://vuejs.org/) 3.0 也使用 TypeScript 进行了重构。\n\n## 适合人群\n\n本书适合以下人群\n\n- 熟悉 JavaScript，至少阅读过一遍[《JavaScript 高级程序设计》](https://book.douban.com/subject/10546125/)\n- 了解 ES6，推荐阅读 [ECMAScript 6 入门]\n- 了解 Node.js，会用 npm 安装及使用一些工具\n- 想了解 TypeScript 或者想对 TypeScript 有更深的理解\n\n本书**不适合**以下人群\n\n- 没有系统学习过 JavaScript\n- 已经能够很熟练的运用 TypeScript\n\n## 评价\n\n> 《TypeScript 入门教程》全面介绍了 TypeScript 强大的类型系统，完整而简洁，示例丰富，比官方文档更易读，非常适合作为初学者学习 TypeScript 的第一本书。\n>\n> —— [阮一峰](https://github.com/ruanyf)\n\n## 版权许可\n\n本书采用「保持署名—非商用」创意共享 4.0 许可证。\n\n只要保持原作者署名和非商用，您可以自由地阅读、分享、修改本书。\n\n详细的法律条文请参见[创意共享](http://creativecommons.org/licenses/by-nc/4.0/)网站。\n\n## 相关资料\n\n- [TypeScript 官网][TypeScript]\n- [Handbook]（[中文版][中文手册]）\n- [ECMAScript 6 入门]\n\n[GitHub]: https://github.com/xcatliu/typescript-tutorial\n[TypeScript]: http://www.typescriptlang.org/\n[Handbook]: http://www.typescriptlang.org/docs/handbook/basic-types.html\n[中文手册]: https://zhongsp.gitbook.io/typescript-handbook/\n[ECMAScript 6 入门]: http://es6.ruanyifeng.com/\n"
  },
  {
    "path": "advanced/README.md",
    "content": "# 进阶\n\n本部分介绍一些高级的类型与技术，具体内容包括：\n\n- [类型别名](type-aliases.md)\n- [字符串字面量类型](string-literal-types.md)\n- [元组](tuple.md)\n- [枚举](enum.md)\n- [类](class.md)\n- [类与接口](class-and-interfaces.md)\n- [泛型](generics.md)\n- [声明合并](declaration-merging.md)\n- [扩展阅读](further-reading.md)\n"
  },
  {
    "path": "advanced/class-and-interfaces.md",
    "content": "# 类与接口\n\n[之前学习过](../basics/type-of-object-interfaces.md)，接口（Interfaces）可以用于对「对象的形状（Shape）」进行描述。\n\n这一章主要介绍接口的另一个用途，对类的一部分行为进行抽象。\n\n## 类实现接口\n\n实现（implements）是面向对象中的一个重要概念。一般来讲，一个类只能继承自另一个类，有时候不同类之间可以有一些共有的特性，这时候就可以把特性提取成接口（interfaces），用 `implements` 关键字来实现。这个特性大大提高了面向对象的灵活性。\n\n举例来说，门是一个类，防盗门是门的子类。如果防盗门有一个报警器的功能，我们可以简单的给防盗门添加一个报警方法。这时候如果有另一个类，车，也有报警器的功能，就可以考虑把报警器提取出来，作为一个接口，防盗门和车都去实现它：\n\n```ts\ninterface Alarm {\n    alert(): void;\n}\n\nclass Door {\n}\n\nclass SecurityDoor extends Door implements Alarm {\n    alert() {\n        console.log('SecurityDoor alert');\n    }\n}\n\nclass Car implements Alarm {\n    alert() {\n        console.log('Car alert');\n    }\n}\n```\n\n一个类可以实现多个接口：\n\n```ts\ninterface Alarm {\n    alert(): void;\n}\n\ninterface Light {\n    lightOn(): void;\n    lightOff(): void;\n}\n\nclass Car implements Alarm, Light {\n    alert() {\n        console.log('Car alert');\n    }\n    lightOn() {\n        console.log('Car light on');\n    }\n    lightOff() {\n        console.log('Car light off');\n    }\n}\n```\n\n上例中，`Car` 实现了 `Alarm` 和 `Light` 接口，既能报警，也能开关车灯。\n\n## 接口继承接口\n\n接口与接口之间可以是继承关系：\n\n```ts\ninterface Alarm {\n    alert(): void;\n}\n\ninterface LightableAlarm extends Alarm {\n    lightOn(): void;\n    lightOff(): void;\n}\n```\n\n这很好理解，`LightableAlarm` 继承了 `Alarm`，除了拥有 `alert` 方法之外，还拥有两个新方法 `lightOn` 和 `lightOff`。\n\n## 接口继承类\n\n常见的面向对象语言中，接口是不能继承类的，但是在 TypeScript 中却是可以的：\n\n```ts\nclass Point {\n    x: number;\n    y: number;\n    constructor(x: number, y: number) {\n        this.x = x;\n        this.y = y;\n    }\n}\n\ninterface Point3d extends Point {\n    z: number;\n}\n\nlet point3d: Point3d = {x: 1, y: 2, z: 3};\n```\n\n为什么 TypeScript 会支持接口继承类呢？\n\n实际上，当我们在声明 `class Point` 时，除了会创建一个名为 `Point` 的类之外，同时也创建了一个名为 `Point` 的类型（实例的类型）。\n\n所以我们既可以将 `Point` 当做一个类来用（使用 `new Point` 创建它的实例）：\n\n```ts\nclass Point {\n    x: number;\n    y: number;\n    constructor(x: number, y: number) {\n        this.x = x;\n        this.y = y;\n    }\n}\n\nconst p = new Point(1, 2);\n```\n\n也可以将 `Point` 当做一个类型来用（使用 `: Point` 表示参数的类型）：\n\n```ts\nclass Point {\n    x: number;\n    y: number;\n    constructor(x: number, y: number) {\n        this.x = x;\n        this.y = y;\n    }\n}\n\nfunction printPoint(p: Point) {\n    console.log(p.x, p.y);\n}\n\nprintPoint(new Point(1, 2));\n```\n\n这个例子实际上可以等价于：\n\n```ts\nclass Point {\n    x: number;\n    y: number;\n    constructor(x: number, y: number) {\n        this.x = x;\n        this.y = y;\n    }\n}\n\ninterface PointInstanceType {\n    x: number;\n    y: number;\n}\n\nfunction printPoint(p: PointInstanceType) {\n    console.log(p.x, p.y);\n}\n\nprintPoint(new Point(1, 2));\n```\n\n上例中我们新声明的 `PointInstanceType` 类型，与声明 `class Point` 时创建的 `Point` 类型是等价的。\n\n所以回到 `Point3d` 的例子中，我们就能很容易的理解为什么 TypeScript 会支持接口继承类了：\n\n```ts\nclass Point {\n    x: number;\n    y: number;\n    constructor(x: number, y: number) {\n        this.x = x;\n        this.y = y;\n    }\n}\n\ninterface PointInstanceType {\n    x: number;\n    y: number;\n}\n\n// 等价于 interface Point3d extends PointInstanceType\ninterface Point3d extends Point {\n    z: number;\n}\n\nlet point3d: Point3d = {x: 1, y: 2, z: 3};\n```\n\n当我们声明 `interface Point3d extends Point` 时，`Point3d` 继承的实际上是类 `Point` 的实例的类型。\n\n换句话说，可以理解为定义了一个接口 `Point3d` 继承另一个接口 `PointInstanceType`。\n\n所以「接口继承类」和「接口继承接口」没有什么本质的区别。\n\n值得注意的是，`PointInstanceType` 相比于 `Point`，缺少了 `constructor` 方法，这是因为声明 `Point` 类时创建的 `Point` 类型是不包含构造函数的。另外，除了构造函数是不包含的，静态属性或静态方法也是不包含的（实例的类型当然不应该包括构造函数、静态属性或静态方法）。\n\n换句话说，声明 `Point` 类时创建的 `Point` 类型只包含其中的实例属性和实例方法：\n\n```ts\nclass Point {\n    /** 静态属性，坐标系原点 */\n    static origin = new Point(0, 0);\n    /** 静态方法，计算与原点距离 */\n    static distanceToOrigin(p: Point) {\n        return Math.sqrt(p.x * p.x + p.y * p.y);\n    }\n    /** 实例属性，x 轴的值 */\n    x: number;\n    /** 实例属性，y 轴的值 */\n    y: number;\n    /** 构造函数 */\n    constructor(x: number, y: number) {\n        this.x = x;\n        this.y = y;\n    }\n    /** 实例方法，打印此点 */\n    printPoint() {\n        console.log(this.x, this.y);\n    }\n}\n\ninterface PointInstanceType {\n    x: number;\n    y: number;\n    printPoint(): void;\n}\n\nlet p1: Point;\nlet p2: PointInstanceType;\n```\n\n上例中最后的类型 `Point` 和类型 `PointInstanceType` 是等价的。\n\n同样的，在接口继承类的时候，也只会继承它的实例属性和实例方法。\n\n## 参考\n\n- [Interfaces](http://www.typescriptlang.org/docs/handbook/interfaces.html)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Interfaces.html)）\n"
  },
  {
    "path": "advanced/class.md",
    "content": "# 类\n\n传统方法中，JavaScript 通过构造函数实现类的概念，通过原型链实现继承。而在 ES6 中，我们终于迎来了 `class`。\n\nTypeScript 除了实现了所有 ES6 中的类的功能以外，还添加了一些新的用法。\n\n这一节主要介绍类的用法，下一节再介绍如何定义类的类型。\n\n## 类的概念\n\n虽然 JavaScript 中有类的概念，但是可能大多数 JavaScript 程序员并不是非常熟悉类，这里对类相关的概念做一个简单的介绍。\n\n- 类（Class）：定义了一件事物的抽象特点，包含它的属性和方法\n- 对象（Object）：类的实例，通过 `new` 生成\n- 面向对象（OOP）的三大特性：封装、继承、多态\n- 封装（Encapsulation）：将对数据的操作细节隐藏起来，只暴露对外的接口。外界调用端不需要（也不可能）知道细节，就能通过对外提供的接口来访问该对象，同时也保证了外界无法任意更改对象内部的数据\n- 继承（Inheritance）：子类继承父类，子类除了拥有父类的所有特性外，还有一些更具体的特性\n- 多态（Polymorphism）：由继承而产生了相关的不同的类，对同一个方法可以有不同的响应。比如 `Cat` 和 `Dog` 都继承自 `Animal`，但是分别实现了自己的 `eat` 方法。此时针对某一个实例，我们无需了解它是 `Cat` 还是 `Dog`，就可以直接调用 `eat` 方法，程序会自动判断出来应该如何执行 `eat`\n- 存取器（getter & setter）：用以改变属性的读取和赋值行为\n- 修饰符（Modifiers）：修饰符是一些关键字，用于限定成员或类型的性质。比如 `public` 表示公有属性或方法\n- 抽象类（Abstract Class）：抽象类是供其他类继承的基类，抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现\n- 接口（Interfaces）：不同类之间公有的属性或方法，可以抽象成一个接口。接口可以被类实现（implements）。一个类只能继承自另一个类，但是可以实现多个接口\n\n## ES6 中类的用法\n\n下面我们先回顾一下 ES6 中类的用法，更详细的介绍可以参考 [ECMAScript 6 入门 - Class]。\n\n### 属性和方法\n\n使用 `class` 定义类，使用 `constructor` 定义构造函数。\n\n通过 `new` 生成新实例的时候，会自动调用构造函数。\n\n```js\nclass Animal {\n    name;\n    constructor(name) {\n        this.name = name;\n    }\n    sayHi() {\n        return `My name is ${this.name}`;\n    }\n}\n\nlet a = new Animal('Jack');\nconsole.log(a.sayHi()); // My name is Jack\n```\n\n### 类的继承\n\n使用 `extends` 关键字实现继承，子类中使用 `super` 关键字来调用父类的构造函数和方法。\n\n```js\nclass Cat extends Animal {\n  constructor(name) {\n    super(name); // 调用父类的 constructor(name)\n    console.log(this.name);\n  }\n  sayHi() {\n    return 'Meow, ' + super.sayHi(); // 调用父类的 sayHi()\n  }\n}\n\nlet c = new Cat('Tom'); // Tom\nconsole.log(c.sayHi()); // Meow, My name is Tom\n```\n\n### 存取器\n\n使用 getter 和 setter 可以改变属性的赋值和读取行为：\n\n```js\nclass Animal {\n  constructor(name) {\n    this.name = name;\n  }\n  get name() {\n    return 'Jack';\n  }\n  set name(value) {\n    console.log('setter: ' + value);\n  }\n}\n\nlet a = new Animal('Kitty'); // setter: Kitty\na.name = 'Tom'; // setter: Tom\nconsole.log(a.name); // Jack\n```\n\n### 静态方法\n\n使用 `static` 修饰符修饰的方法称为静态方法，它们不需要实例化，而是直接通过类来调用：\n\n```js\nclass Animal {\n  static isAnimal(a) {\n    return a instanceof Animal;\n  }\n}\n\nlet a = new Animal('Jack');\nAnimal.isAnimal(a); // true\na.isAnimal(a); // TypeError: a.isAnimal is not a function\n```\n\n## ES7 中类的用法\n\nES7 中有一些关于类的提案，TypeScript 也实现了它们，这里做一个简单的介绍。\n\n### 实例属性\n\nES6 中实例的属性只能通过构造函数中的 `this.xxx` 来定义，ES7 提案中可以直接在类里面定义：\n\n```js\nclass Animal {\n  name = 'Jack';\n\n  constructor() {\n    // ...\n  }\n}\n\nlet a = new Animal();\nconsole.log(a.name); // Jack\n```\n\n### 静态属性\n\nES7 提案中，可以使用 `static` 定义一个静态属性：\n\n```js\nclass Animal {\n  static num = 42;\n\n  constructor() {\n    // ...\n  }\n}\n\nconsole.log(Animal.num); // 42\n```\n\n## TypeScript 中类的用法\n\n### public private 和 protected\n\nTypeScript 可以使用三种访问修饰符（Access Modifiers），分别是 `public`、`private` 和 `protected`。\n\n- `public` 修饰的属性或方法是公有的，可以在任何地方被访问到，默认所有的属性和方法都是 `public` 的\n- `private` 修饰的属性或方法是私有的，不能在声明它的类的外部访问\n- `protected` 修饰的属性或方法是受保护的，它和 `private` 类似，区别是它在子类中也是允许被访问的\n\n下面举一些例子：\n\n```ts\nclass Animal {\n  public name;\n  public constructor(name) {\n    this.name = name;\n  }\n}\n\nlet a = new Animal('Jack');\nconsole.log(a.name); // Jack\na.name = 'Tom';\nconsole.log(a.name); // Tom\n```\n\n上面的例子中，`name` 被设置为了 `public`，所以直接访问实例的 `name` 属性是允许的。\n\n很多时候，我们希望有的属性是无法直接存取的，这时候就可以用 `private` 了：\n\n```ts\nclass Animal {\n  private name;\n  public constructor(name) {\n    this.name = name;\n  }\n}\n\nlet a = new Animal('Jack');\nconsole.log(a.name);\na.name = 'Tom';\n\n// index.ts(9,13): error TS2341: Property 'name' is private and only accessible within class 'Animal'.\n// index.ts(10,1): error TS2341: Property 'name' is private and only accessible within class 'Animal'.\n```\n\n需要注意的是，TypeScript 编译之后的代码中，并没有限制 `private` 属性在外部的可访问性。\n\n上面的例子编译后的代码是：\n\n```js\nvar Animal = (function () {\n  function Animal(name) {\n    this.name = name;\n  }\n  return Animal;\n})();\nvar a = new Animal('Jack');\nconsole.log(a.name);\na.name = 'Tom';\n```\n\n使用 `private` 修饰的属性或方法，在子类中也是不允许访问的：\n\n```ts\nclass Animal {\n  private name;\n  public constructor(name) {\n    this.name = name;\n  }\n}\n\nclass Cat extends Animal {\n  constructor(name) {\n    super(name);\n    console.log(this.name);\n  }\n}\n\n// index.ts(11,17): error TS2341: Property 'name' is private and only accessible within class 'Animal'.\n```\n\n而如果是用 `protected` 修饰，则允许在子类中访问：\n\n```ts\nclass Animal {\n  protected name;\n  public constructor(name) {\n    this.name = name;\n  }\n}\n\nclass Cat extends Animal {\n  constructor(name) {\n    super(name);\n    console.log(this.name);\n  }\n}\n```\n\n当构造函数修饰为 `private` 时，该类不允许被继承或者实例化：\n\n```ts\nclass Animal {\n  public name;\n  private constructor(name) {\n    this.name = name;\n  }\n}\nclass Cat extends Animal {\n  constructor(name) {\n    super(name);\n  }\n}\n\nlet a = new Animal('Jack');\n\n// index.ts(7,19): TS2675: Cannot extend a class 'Animal'. Class constructor is marked as private.\n// index.ts(13,9): TS2673: Constructor of class 'Animal' is private and only accessible within the class declaration.\n```\n\n当构造函数修饰为 `protected` 时，该类只允许被继承：\n\n```ts\nclass Animal {\n  public name;\n  protected constructor(name) {\n    this.name = name;\n  }\n}\nclass Cat extends Animal {\n  constructor(name) {\n    super(name);\n  }\n}\n\nlet a = new Animal('Jack');\n\n// index.ts(13,9): TS2674: Constructor of class 'Animal' is protected and only accessible within the class declaration.\n```\n\n### 参数属性\n\n修饰符和`readonly`还可以使用在构造函数参数中，等同于类中定义该属性同时给该属性赋值，使代码更简洁。\n\n```ts\nclass Animal {\n  // public name: string;\n  public constructor(public name) {\n    // this.name = name;\n  }\n}\n```\n\n### readonly\n\n只读属性关键字，只允许出现在属性声明或索引签名或构造函数中。\n\n```ts\nclass Animal {\n  readonly name;\n  public constructor(name) {\n    this.name = name;\n  }\n}\n\nlet a = new Animal('Jack');\nconsole.log(a.name); // Jack\na.name = 'Tom';\n\n// index.ts(10,3): TS2540: Cannot assign to 'name' because it is a read-only property.\n```\n\n注意如果 `readonly` 和其他访问修饰符同时存在的话，需要写在其后面。\n\n```ts\nclass Animal {\n  // public readonly name;\n  public constructor(public readonly name) {\n    // this.name = name;\n  }\n}\n```\n\n### 抽象类\n\n`abstract` 用于定义抽象类和其中的抽象方法。\n\n什么是抽象类？\n\n首先，抽象类是不允许被实例化的：\n\n```ts\nabstract class Animal {\n  public name;\n  public constructor(name) {\n    this.name = name;\n  }\n  public abstract sayHi();\n}\n\nlet a = new Animal('Jack');\n\n// index.ts(9,11): error TS2511: Cannot create an instance of the abstract class 'Animal'.\n```\n\n上面的例子中，我们定义了一个抽象类 `Animal`，并且定义了一个抽象方法 `sayHi`。在实例化抽象类的时候报错了。\n\n其次，抽象类中的抽象方法必须被子类实现：\n\n```ts\nabstract class Animal {\n  public name;\n  public constructor(name) {\n    this.name = name;\n  }\n  public abstract sayHi();\n}\n\nclass Cat extends Animal {\n  public eat() {\n    console.log(`${this.name} is eating.`);\n  }\n}\n\nlet cat = new Cat('Tom');\n\n// index.ts(9,7): error TS2515: Non-abstract class 'Cat' does not implement inherited abstract member 'sayHi' from class 'Animal'.\n```\n\n上面的例子中，我们定义了一个类 `Cat` 继承了抽象类 `Animal`，但是没有实现抽象方法 `sayHi`，所以编译报错了。\n\n下面是一个正确使用抽象类的例子：\n\n```ts\nabstract class Animal {\n  public name;\n  public constructor(name) {\n    this.name = name;\n  }\n  public abstract sayHi();\n}\n\nclass Cat extends Animal {\n  public sayHi() {\n    console.log(`Meow, My name is ${this.name}`);\n  }\n}\n\nlet cat = new Cat('Tom');\n```\n\n上面的例子中，我们实现了抽象方法 `sayHi`，编译通过了。\n\n需要注意的是，即使是抽象方法，TypeScript 的编译结果中，仍然会存在这个类，上面的代码的编译结果是：\n\n```js\nvar __extends =\n  (this && this.__extends) ||\n  function (d, b) {\n    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];\n    function __() {\n      this.constructor = d;\n    }\n    d.prototype = b === null ? Object.create(b) : ((__.prototype = b.prototype), new __());\n  };\nvar Animal = (function () {\n  function Animal(name) {\n    this.name = name;\n  }\n  return Animal;\n})();\nvar Cat = (function (_super) {\n  __extends(Cat, _super);\n  function Cat() {\n    _super.apply(this, arguments);\n  }\n  Cat.prototype.sayHi = function () {\n    console.log('Meow, My name is ' + this.name);\n  };\n  return Cat;\n})(Animal);\nvar cat = new Cat('Tom');\n```\n\n## 类的类型\n\n给类加上 TypeScript 的类型很简单，与接口类似：\n\n```ts\nclass Animal {\n  name: string;\n  constructor(name: string) {\n    this.name = name;\n  }\n  sayHi(): string {\n    return `My name is ${this.name}`;\n  }\n}\n\nlet a: Animal = new Animal('Jack');\nconsole.log(a.sayHi()); // My name is Jack\n```\n\n## 参考\n\n- [Classes](http://www.typescriptlang.org/docs/handbook/classes.html)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Classes.html)）\n- [ECMAScript 6 入门 - Class]\n\n[ecmascript 6 入门 - class]: http://es6.ruanyifeng.com/#docs/class\n"
  },
  {
    "path": "advanced/declaration-merging.md",
    "content": "# 声明合并\n\n如果定义了两个相同名字的函数、接口或类，那么它们会合并成一个类型：\n\n## 函数的合并\n\n[之前学习过](../basics/type-of-function.md#重载)，我们可以使用重载定义多个函数类型：\n\n```ts\nfunction reverse(x: number): number;\nfunction reverse(x: string): string;\nfunction reverse(x: number | string): number | string {\n    if (typeof x === 'number') {\n        return Number(x.toString().split('').reverse().join(''));\n    } else if (typeof x === 'string') {\n        return x.split('').reverse().join('');\n    }\n}\n```\n\n## 接口的合并\n\n接口中的属性在合并时会简单的合并到一个接口中：\n\n```ts\ninterface Alarm {\n    price: number;\n}\ninterface Alarm {\n    weight: number;\n}\n```\n\n相当于：\n\n```ts\ninterface Alarm {\n    price: number;\n    weight: number;\n}\n```\n\n注意，**合并的属性的类型必须是唯一的**：\n\n```ts\ninterface Alarm {\n    price: number;\n}\ninterface Alarm {\n    price: number;  // 虽然重复了，但是类型都是 `number`，所以不会报错\n    weight: number;\n}\n```\n\n```ts\ninterface Alarm {\n    price: number;\n}\ninterface Alarm {\n    price: string;  // 类型不一致，会报错\n    weight: number;\n}\n\n// index.ts(5,3): error TS2403: Subsequent variable declarations must have the same type.  Variable 'price' must be of type 'number', but here has type 'string'.\n```\n\n接口中方法的合并，与函数的合并一样：\n\n```ts\ninterface Alarm {\n    price: number;\n    alert(s: string): string;\n}\ninterface Alarm {\n    weight: number;\n    alert(s: string, n: number): string;\n}\n```\n\n相当于：\n\n```ts\ninterface Alarm {\n    price: number;\n    weight: number;\n    alert(s: string): string;\n    alert(s: string, n: number): string;\n}\n```\n\n## 类的合并\n\n类的合并与接口的合并规则一致。\n\n## 参考\n\n- [Declaration Merging](http://www.typescriptlang.org/docs/handbook/declaration-merging.html)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Declaration%20Merging.html)）\n"
  },
  {
    "path": "advanced/decorator.md",
    "content": "# 装饰器\n\n写在前面：本章只介绍 TypeScript 5.0+ 的装饰器用法，对于 5.0 以下的版本，请参考 [TypeScript 官方文档](https://www.typescriptlang.org/docs/handbook/decorators.html)\n\n## 什么是装饰器\n\n首先，什么是装饰器呢？[维基百科](https://en.wikipedia.org/wiki/Decorator_pattern)是这么说的：\n\n> In [object-oriented programming](https://en.wikipedia.org/wiki/Object-oriented_programming), the **decorator pattern** is a [design pattern](https://en.wikipedia.org/wiki/Design_pattern_(computer_science)) that allows behavior to be added to an individual [object](https://en.wikipedia.org/wiki/Object_(computer_science)), dynamically, without affecting the behavior of other instances of the same [class](https://en.wikipedia.org/wiki/Class_(computer_science)).\n\n本人的蹩足翻译：在 OOP (面向对象编程)中，装饰器模式是一种允许动态地往一个对象上添加自定义行为，而又不影响该对象所属的类的其他实例的一种设计模式。\n\n> 什么是 OOP 和类？[前面的章节](https://ts.xcatliu.com/advanced/class.html)做过介绍。\n\n这句话未免过于拗口了，我们不妨换个角度去切入。\n\n## 装饰器的使用场景\n\n要知道，一切设计模式的诞生，都是为了解决某个问题。在 JavaScript 的世界中，装饰器通常出现于以下场景：\n\n1. 提供一种易读且容易实现的方式，修改类或者类的方法，避免出现大量重复的代码。\n    \n    下面以修改类的方法为例。\n    \n    首先，假设我们有一个 `Animal` 类：\n    \n    ```ts\n    class Animal {\n      type: string\n      constructor(type: string) {\n    \t  this.type = type\n      }\n      \n      greet() {\n        console.log(`Hello, I'm a(n) ${this.type}!`)\n      }\n    }\n    \n    const xcat = new Animal('cat')\n    xcat.greet() // Hello, I'm a(n) cat!\n    ```\n    \n    该类有一个 greet 方法，和调用方打招呼。\n    \n    假如说，我还希望根据不同的 `type`，往 console 打印不同动物的叫声呢？\n    \n    聪明的你或许想到了，这不就是**类的继承**吗！在子类的 `greet()` 方法中，实现不同的逻辑，再调用 `super.greet()` 即可。\n    \n    ```ts\n    class Xcat extends Animal {\n      constructor() {\n        super('cat')\n      }\n      \n      greet() {\n        console.log('meow~ meow~')\n        super.greet()\n      }\n    }\n    \n    const xcat = new Xcat()\n    xcat.greet() // meow~ meow~\n                 // Hello, I'm a(n) cat!\n    ```\n    \n    用装饰器实现，也不妨为一种思路，比如在 `Animal` 类中，为 `greet()` 方法添加「打印不同动物叫声的」行为:\n    \n    ```ts\n    class Animal {\n      type: string\n      constructor(type: string) {\n    \t  this.type = type\n      }\n    \n      @yelling\n      greet() {\n        console.log(`Hello, I'm a(n) ${this.type}!`)\n      }\n    }\n    \n    const typeToYellingMap = {\n      cat: 'meow~ meow~'\n    }\n    \n    function yelling(originalMethod: any, context: ClassMethodDecoratorContext) {\n      return function(...args: any[]) {\n        console.log(typeToYellingMap[this.type])\n        originalMethod.call(this, ...args)\n      }\n    }\n    \n    const xcat = new Animal('cat')\n    xcat.greet() // meow~ meow~\n                 // Hello, I'm a(n) cat!\n    ```\n    \n    在 `Animal.greet()` 方法上出现的 `@yelling` ，就是 TypeScript 中装饰器的写法，即 @ + 函数名的组合。\n    \n    上述示例对装饰器的应用属于**方法装饰器**，此类装饰器本身接收两个参数，一是被装饰的方法，二是方法装饰器的上下文。方法装饰器应返回一个函数，此函数在运行时真正被执行。在上述例子中，我们在装饰器返回的函数中做了两件事情：\n    \n    1. 打印相应类别的动物的叫声。\n    2. 调用 `originalMethod.call(this, …args)` ，确保原方法（即装饰器所装饰的方法）能够正确地被执行。\n2. 结合「**依赖注入**」这一设计模式，优化模块与 class 的依赖关系。\n    \n    什么是依赖注入呢？引用同事 [zio](https://github.com/ziofat) 的原话：\n    \n    > **依赖注入其实是将一个模块所依赖的部分作为参数传入，而不是由模块自己去构造。**\n    \n    可见，依赖注入解决了实际工程项目中，类、模块间依赖关系层级复杂的问题，将构造单例的行为交由实现依赖注入的框架去处理。\n    \n    举个例子：\n\n    ```ts\n    @injectable\n    class Dog implements IAnimal {\n      sayHi() {\n        console.log('woof woof woof')\n      }\n    }\n    \n    @injectable\n    class Cat implements IAnimal {\n      sayHi() {\n        console.log('meow meow meow')\n      }\n    }\n    \n    class AnimalService {\n      constructor(\n        @inject dog: Dog\n        @inject cat: Cat\n      ) {\n        this._dog = dog\n        this._cat = cat\n      }\n      \n      sayHiByDog() {\n        this._dog.sayHi()\n      }\n      \n      sayHiByCat() {\n        this._cat.sayHi()\n      }\n    }\n    ```\n    \n    在上述代码中，`@injectable` 将一个类标记为「可被注入的」，在面向业务的类（即 `AnimalService`）中，使用 `@inject` 注入此类的单例，实现了「依赖倒置」。注意到这里的 `implements IAnimal` 用法，也是实战中依赖注入运用的精妙之处 —— 关心接口，而非具体实现。\n    \n3. 实现「AOP」，即 Aspect-oriented programming，面向切面编程。\n    \n    所谓的「切面」，可以理解成，在复杂的各个业务维度中，只关注一个维度的事务。\n    \n    例如，使用装饰器，实现对类的某个方法的执行时间记录：\n    \n    ```ts\n    class MyService {\n      @recordExecution\n      myFn() {\n        // do something...\n      }\n    }\n    \n    function recordExecution(originalMethod: any, context: ClassMethodDecoratorContext) {\n      return function(...args: any[]) {\n        console.time('mark execution')\n        originalMethod.call(this, ...args)\n        console.timeEnd('mark execution')\n      }\n    }\n    ```\n    \n\n## 装饰器的类别\n\n通过以上例子，相信读者已经对装饰器有一定了解，且认识到了装饰器在一些场景的强大之处。在此引用[阮一峰 es6 教程](https://es6.ruanyifeng.com/#docs/decorator#%E7%AE%80%E4%BB%8B%EF%BC%88%E6%96%B0%E8%AF%AD%E6%B3%95%EF%BC%89)稍做总结：\n\n> 装饰器是一种函数，写成`@ + 函数名`，可以用来装饰四种类型的值。\n> \n> - 类\n> - 类的属性\n> - 类的方法\n> - 属性存取器（accessor, getter, setter）\n\n> 装饰器的执行步骤如下。\n> \n> 1. 计算各个装饰器的值，按照从左到右，从上到下的顺序。\n> 2. 调用方法装饰器。\n> 3. 调用类装饰器。\n\n不管是哪种类型的装饰器，它们的函数签名都可以认为是一致的，即均接收 `value`, `context` 两个参数，前者指被装饰的对象，后者指一个存储了上下文信息的对象。\n\n## context 与 metadata 二三讲\n\n四种装饰器的 context，均包含以下信息：\n\n- kind\n    \n    描述被装饰的 value 的类型，可取 `class`, `method`, `field`, `getter`, `setter`, `accessor` 这些值。\n    \n- name\n    \n    描述被装饰的 value 的名字。\n    \n- addInitializer\n    \n    一个方法，接收一个回调函数，使得开发者可以侵入 value 的初始化过程作修改。\n    \n    对 `class` 来说，这个回调函数会在类定义最终确认后调用，即相当于在初始化过程的最后一步。\n    \n    对其他的 value 来说，如果是被 `static` 所修饰的，则会在类定义期间被调用，且早于其他静态属性的赋值过程；否则，会在类初始化期间被调用，且早于 value 自身的初始化。\n    \n    以下是 `@bound` 类方法装饰器的例子，该装饰器自动为方法绑定 `this`：\n    \n    ```ts\n    const bound = (value, context: ClassMemberDecoratorContext) {\n      if (context.private) throw new TypeError(\"Not supported on private methods.\");\n      context.addInitializer(function () {\n          this[context.name] = this[context.name].bind(this);\n      });\n    }\n    ```\n    \n- metadata\n    \n    和装饰器类似，[metadata](https://github.com/tc39/proposal-decorator-metadata) 也是处于 stage 3 阶段的一个提案。装饰器只能访问到类原型链、类实例的相关数据，而 metadata 给了开发者更大的自由，让程序于运行时访问到编译时决定的元数据。\n    \n    举个例子：\n    \n    ```ts\n    function meta(key, value) {\n      return (_, context) => {\n        context.metadata[key] = value;\n      };\n    }\n    \n    @meta('a', 'x')\n    class C {\n      @meta('b', 'y')\n      m() {}\n    }\n    \n    C[Symbol.metadata].a; // 'x'\n    C[Symbol.metadata].b; // 'y'\n    ```\n    \n    在上述程序中，我们通过访问类的 `Symbol.metadata` ，读取到了 meta 装饰器所写入的元数据。对元数据的访问，有且仅有这一种形式。\n    \n    注意一点，metadata 是作用在类上的，即使它的位置在类方法上。想实现细粒度的元数据存储，可以考虑手动维护若干 `WeakMap`。\n    \n\n除了类装饰器以外，其他3种装饰器的 context 还拥有以下 3 个字段：\n\n- static\n    \n    布尔值，描述 value 是否为 static 所修饰。\n    \n- private\n    \n    布尔值，描述 value 是否为 private 所修饰。\n    \n- access\n    \n    一个对象，可在运行时访问 value 相关数据。\n    \n    以类方法装饰器为例，用 `access.get` 可在运行时读取方法值，`access.has` 可在运行时查询对象上是否有某方法，举个例子：\n    \n    ```ts\n    const typeToYellingMap = {\n      cat: 'meow~ meow~',\n    }\n    \n    let yellingMethodContext: ClassMethodDecoratorContext\n    \n    class Animal {\n      type: string\n      constructor(type: string) {\n        this.type = type\n      }\n    \n      @yelling\n      greet() {\n        console.log(`Hello, I'm a(n) ${this.type}!`)\n      }\n    \n      accessor y = 1\n    }\n    \n    function yelling(originalMethod: any, context: ClassMethodDecoratorContext) {\n      yellingMethodContext = context\n      return function (this: any, ...args: any[]) {\n        console.log(typeToYellingMap[this.type as keyof typeof typeToYellingMap])\n        originalMethod.call(this, ...args)\n      }\n    }\n    \n    const xcat = new Animal('cat')\n    xcat.greet() // meow~ meow~\n    // Hello, I'm a(n) cat!\n    yellingMethodContext.access.get(xcat).call(xcat) // meow~ meow~\n    // Hello, I'm a(n) cat!\n    console.log(yellingMethodContext.access.has(xcat)) // true\n    ```\n    \n    `getter` 类别的装饰器，其 `context.access` 同样拥有 `has`, `get` 两个方法。\n    \n    对于 `setter` 类别的装饰器，则是 `has` 与 `set` 方法。\n    \n    `filed` 与 `accessor` 类别的装饰器，拥有 `has`, `get`, `set` 全部三个方法。\n"
  },
  {
    "path": "advanced/enum.md",
    "content": "# 枚举\n\n枚举（Enum）类型用于取值被限定在一定范围内的场景，比如一周只能有七天，颜色限定为红绿蓝等。\n\n## 简单的例子\n\n枚举使用 `enum` 关键字来定义：\n\n```ts\nenum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};\n```\n\n枚举成员会被赋值为从 `0` 开始递增的数字，同时也会对枚举值到枚举名进行反向映射：\n\n```ts\nenum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};\n\nconsole.log(Days[\"Sun\"] === 0); // true\nconsole.log(Days[\"Mon\"] === 1); // true\nconsole.log(Days[\"Tue\"] === 2); // true\nconsole.log(Days[\"Sat\"] === 6); // true\n\nconsole.log(Days[0] === \"Sun\"); // true\nconsole.log(Days[1] === \"Mon\"); // true\nconsole.log(Days[2] === \"Tue\"); // true\nconsole.log(Days[6] === \"Sat\"); // true\n```\n\n事实上，上面的例子会被编译为：\n\n```js\nvar Days;\n(function (Days) {\n    Days[Days[\"Sun\"] = 0] = \"Sun\";\n    Days[Days[\"Mon\"] = 1] = \"Mon\";\n    Days[Days[\"Tue\"] = 2] = \"Tue\";\n    Days[Days[\"Wed\"] = 3] = \"Wed\";\n    Days[Days[\"Thu\"] = 4] = \"Thu\";\n    Days[Days[\"Fri\"] = 5] = \"Fri\";\n    Days[Days[\"Sat\"] = 6] = \"Sat\";\n})(Days || (Days = {}));\n```\n\n## 手动赋值\n\n我们也可以给枚举项手动赋值：\n\n```ts\nenum Days {Sun = 7, Mon = 1, Tue, Wed, Thu, Fri, Sat};\n\nconsole.log(Days[\"Sun\"] === 7); // true\nconsole.log(Days[\"Mon\"] === 1); // true\nconsole.log(Days[\"Tue\"] === 2); // true\nconsole.log(Days[\"Sat\"] === 6); // true\n```\n\n上面的例子中，未手动赋值的枚举项会接着上一个枚举项递增。\n\n如果未手动赋值的枚举项与手动赋值的重复了，TypeScript 是不会察觉到这一点的：\n\n```ts\nenum Days {Sun = 3, Mon = 1, Tue, Wed, Thu, Fri, Sat};\n\nconsole.log(Days[\"Sun\"] === 3); // true\nconsole.log(Days[\"Wed\"] === 3); // true\nconsole.log(Days[3] === \"Sun\"); // false\nconsole.log(Days[3] === \"Wed\"); // true\n```\n\n上面的例子中，递增到 `3` 的时候与前面的 `Sun` 的取值重复了，但是 TypeScript 并没有报错，导致 `Days[3]` 的值先是 `\"Sun\"`，而后又被 `\"Wed\"` 覆盖了。编译的结果是：\n\n```js\nvar Days;\n(function (Days) {\n    Days[Days[\"Sun\"] = 3] = \"Sun\";\n    Days[Days[\"Mon\"] = 1] = \"Mon\";\n    Days[Days[\"Tue\"] = 2] = \"Tue\";\n    Days[Days[\"Wed\"] = 3] = \"Wed\";\n    Days[Days[\"Thu\"] = 4] = \"Thu\";\n    Days[Days[\"Fri\"] = 5] = \"Fri\";\n    Days[Days[\"Sat\"] = 6] = \"Sat\";\n})(Days || (Days = {}));\n```\n\n所以使用的时候需要注意，最好不要出现这种覆盖的情况。\n\n手动赋值的枚举项可以不是数字，此时需要使用类型断言来让 tsc 无视类型检查 (编译出的 js 仍然是可用的)：\n\n```ts\nenum Days {Sun = 7, Mon, Tue, Wed, Thu, Fri, Sat = <any>\"S\"};\n```\n\n```js\nvar Days;\n(function (Days) {\n    Days[Days[\"Sun\"] = 7] = \"Sun\";\n    Days[Days[\"Mon\"] = 8] = \"Mon\";\n    Days[Days[\"Tue\"] = 9] = \"Tue\";\n    Days[Days[\"Wed\"] = 10] = \"Wed\";\n    Days[Days[\"Thu\"] = 11] = \"Thu\";\n    Days[Days[\"Fri\"] = 12] = \"Fri\";\n    Days[Days[\"Sat\"] = \"S\"] = \"Sat\";\n})(Days || (Days = {}));\n```\n\n当然，手动赋值的枚举项也可以为小数或负数，此时后续未手动赋值的项的递增步长仍为 `1`：\n\n```ts\nenum Days {Sun = 7, Mon = 1.5, Tue, Wed, Thu, Fri, Sat};\n\nconsole.log(Days[\"Sun\"] === 7); // true\nconsole.log(Days[\"Mon\"] === 1.5); // true\nconsole.log(Days[\"Tue\"] === 2.5); // true\nconsole.log(Days[\"Sat\"] === 6.5); // true\n```\n\n## 常数项和计算所得项\n\n枚举项有两种类型：常数项（constant member）和计算所得项（computed member）。\n\n前面我们所举的例子都是常数项，一个典型的计算所得项的例子：\n\n```ts\nenum Color {Red, Green, Blue = \"blue\".length};\n```\n\n上面的例子中，`\"blue\".length` 就是一个计算所得项。\n\n上面的例子不会报错，但是**如果紧接在计算所得项后面的是未手动赋值的项，那么它就会因为无法获得初始值而报错**：\n\n```ts\nenum Color {Red = \"red\".length, Green, Blue};\n\n// index.ts(1,33): error TS1061: Enum member must have initializer.\n// index.ts(1,40): error TS1061: Enum member must have initializer.\n```\n\n下面是常数项和计算所得项的完整定义，部分引用自[中文手册 - 枚举]：\n\n当满足以下条件时，枚举成员被当作是常数：\n\n- 不具有初始化函数并且之前的枚举成员是常数。在这种情况下，当前枚举成员的值为上一个枚举成员的值加 `1`。但第一个枚举元素是个例外。如果它没有初始化方法，那么它的初始值为 `0`。\n- 枚举成员使用常数枚举表达式初始化。常数枚举表达式是 TypeScript 表达式的子集，它可以在编译阶段求值。当一个表达式满足下面条件之一时，它就是一个常数枚举表达式：\n  - 数字字面量\n  - 引用之前定义的常数枚举成员（可以是在不同的枚举类型中定义的）如果这个成员是在同一个枚举类型中定义的，可以使用非限定名来引用\n  - 带括号的常数枚举表达式\n  - `+`, `-`, `~` 一元运算符应用于常数枚举表达式\n  - `+`, `-`, `*`, `/`, `%`, `<<`, `>>`, `>>>`, `&`, `|`, `^` 二元运算符，常数枚举表达式做为其一个操作对象。若常数枚举表达式求值后为 NaN 或 Infinity，则会在编译阶段报错\n\n所有其它情况的枚举成员被当作是需要计算得出的值。\n\n## 常数枚举\n\n常数枚举是使用 `const enum` 定义的枚举类型：\n\n```ts\nconst enum Directions {\n    Up,\n    Down,\n    Left,\n    Right\n}\n\nlet directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];\n```\n\n常数枚举与普通枚举的区别是，它会在编译阶段被删除，并且不能包含计算成员。\n\n上例的编译结果是：\n\n```js\nvar directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */];\n```\n\n假如包含了计算成员，则会在编译阶段报错：\n\n```ts\nconst enum Color {Red, Green, Blue = \"blue\".length};\n\n// index.ts(1,38): error TS2474: In 'const' enum declarations member initializer must be constant expression.\n```\n\n## 外部枚举\n\n外部枚举（Ambient Enums）是使用 `declare enum` 定义的枚举类型：\n\n```ts\ndeclare enum Directions {\n    Up,\n    Down,\n    Left,\n    Right\n}\n\nlet directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];\n```\n\n之前提到过，`declare` 定义的类型只会用于编译时的检查，编译结果中会被删除。\n\n上例的编译结果是：\n\n```js\nvar directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];\n```\n\n外部枚举与声明语句一样，常出现在声明文件中。\n\n同时使用 `declare` 和 `const` 也是可以的：\n\n```ts\ndeclare const enum Directions {\n    Up,\n    Down,\n    Left,\n    Right\n}\n\nlet directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];\n```\n\n编译结果：\n\n```js\nvar directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */];\n```\n\n> TypeScript 的枚举类型的概念[来源于 C#][C# Enum]。\n\n## 参考\n\n- [Enums](http://www.typescriptlang.org/docs/handbook/enums.html)（[中文版][中文手册 - 枚举]）\n- [C# Enum][]\n\n[中文手册 - 枚举]: https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Enums.html\n[C# Enum]: https://msdn.microsoft.com/zh-cn/library/sbbt4032.aspx\n"
  },
  {
    "path": "advanced/further-reading.md",
    "content": "# 扩展阅读\n\n此处记录了[官方手册](http://www.typescriptlang.org/docs/handbook/basic-types.html)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/)）中包含，但是本书未涉及的概念。\n\n我认为它们是一些不重要或者不属于 TypeScript 的概念，所以这里只给出一个简单的释义，详细内容可以点击链接深入理解。\n\n- [Never](http://www.typescriptlang.org/docs/handbook/basic-types.html#never)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Basic%20Types.html#never)）：永远不存在值的类型，一般用于错误处理函数\n- [Variable Declarations](http://www.typescriptlang.org/docs/handbook/variable-declarations.html)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Variable%20Declarations.html)）：使用 `let` 和 `const` 替代 `var`，这是 [ES6 的知识](http://es6.ruanyifeng.com/#docs/let)\n- [`this`](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Functions.html#this)：箭头函数的运用，这是 [ES6 的知识](http://es6.ruanyifeng.com/#docs/function)\n- [Using Class Types in Generics](http://www.typescriptlang.org/docs/handbook/generics.html#using-class-types-in-generics)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Generics.html#在泛型里使用类类型)）：创建工厂函数时，需要引用构造函数的类类型\n- [Best common type](http://www.typescriptlang.org/docs/handbook/type-inference.html#best-common-type)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Type%20Inference.html#最佳通用类型)）：数组的类型推论\n- [Contextual Type](http://www.typescriptlang.org/docs/handbook/type-inference.html#contextual-type)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Type%20Inference.html#上下文类型)）：函数输入的类型推论\n- [Type Compatibility](http://www.typescriptlang.org/docs/handbook/type-compatibility.html)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Type%20Compatibility.html)）：允许不严格符合类型，只需要在一定规则下兼容即可\n- [Advanced Types](http://www.typescriptlang.org/docs/handbook/advanced-types.html#intersection-types)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Advanced%20Types.html#交叉类型（intersection-types）)）：使用 `&` 将多种类型的共有部分叠加成一种类型\n- [Type Guards and Differentiating Types](http://www.typescriptlang.org/docs/handbook/advanced-types.html#type-guards-and-differentiating-types)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Advanced%20Types.html#类型保护与区分类型（type-guards-and-differentiating-types）)）：联合类型在一些情况下被识别为特定的类型\n- [Discriminated Unions](http://www.typescriptlang.org/docs/handbook/advanced-types.html#discriminated-unions)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Advanced%20Types.html#可辨识联合（discriminated-unions）)）：使用 `|` 联合多个接口的时候，通过一个共有的属性形成可辨识联合\n- [Polymorphic `this` types](http://www.typescriptlang.org/docs/handbook/advanced-types.html#polymorphic-this-types)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Advanced%20Types.html#多态的this类型)）：父类的某个方法返回 `this`，当子类继承父类后，子类的实例调用此方法，返回的 `this` 能够被 TypeScript 正确的识别为子类的实例。\n- [Symbols](http://www.typescriptlang.org/docs/handbook/symbols.html)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Symbols.html)）：新原生类型，这是 [ES6 的知识](http://es6.ruanyifeng.com/#docs/symbol)\n- [Iterators and Generators](http://www.typescriptlang.org/docs/handbook/iterators-and-generators.html)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Iterators%20and%20Generators.html)）：迭代器，这是 [ES6 的知识](http://es6.ruanyifeng.com/#docs/iterator)\n- [Namespaces](http://www.typescriptlang.org/docs/handbook/namespaces.html)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Namespaces.html)）：避免全局污染，现在已被 [ES6 Module](http://es6.ruanyifeng.com/#docs/module) 替代\n- [Mixins](http://www.typescriptlang.org/docs/handbook/mixins.html)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Mixins.html)）：一种编程模式，与 TypeScript 没有直接关系，可以参考 [ES6 中 Mixin 模式的实现](http://es6.ruanyifeng.com/#docs/class#Mixin模式的实现)\n"
  },
  {
    "path": "advanced/generics.md",
    "content": "# 泛型\n\n泛型（Generics）是指在定义函数、接口或类的时候，不预先指定具体的类型，而在使用的时候再指定类型的一种特性。\n\n## 简单的例子\n\n首先，我们来实现一个函数 `createArray`，它可以创建一个指定长度的数组，同时将每一项都填充一个默认值：\n\n```ts\nfunction createArray(length: number, value: any): Array<any> {\n    let result = [];\n    for (let i = 0; i < length; i++) {\n        result[i] = value;\n    }\n    return result;\n}\n\ncreateArray(3, 'x'); // ['x', 'x', 'x']\n```\n\n上例中，我们使用了[之前提到过的数组泛型](../basics/type-of-array.md#数组泛型)来定义返回值的类型。\n\n这段代码编译不会报错，但是一个显而易见的缺陷是，它并没有准确的定义返回值的类型：\n\n`Array<any>` 允许数组的每一项都为任意类型。但是我们预期的是，数组中每一项都应该是输入的 `value` 的类型。\n\n这时候，泛型就派上用场了：\n\n```ts\nfunction createArray<T>(length: number, value: T): Array<T> {\n    let result: T[] = [];\n    for (let i = 0; i < length; i++) {\n        result[i] = value;\n    }\n    return result;\n}\n\ncreateArray<string>(3, 'x'); // ['x', 'x', 'x']\n```\n\n上例中，我们在函数名后添加了 `<T>`，其中 `T` 用来指代任意输入的类型，在后面的输入 `value: T` 和输出 `Array<T>` 中即可使用了。\n\n接着在调用的时候，可以指定它具体的类型为 `string`。当然，也可以不手动指定，而让类型推论自动推算出来：\n\n```ts\nfunction createArray<T>(length: number, value: T): Array<T> {\n    let result: T[] = [];\n    for (let i = 0; i < length; i++) {\n        result[i] = value;\n    }\n    return result;\n}\n\ncreateArray(3, 'x'); // ['x', 'x', 'x']\n```\n\n## 多个类型参数\n\n定义泛型的时候，可以一次定义多个类型参数：\n\n```ts\nfunction swap<T, U>(tuple: [T, U]): [U, T] {\n    return [tuple[1], tuple[0]];\n}\n\nswap([7, 'seven']); // ['seven', 7]\n```\n\n上例中，我们定义了一个 `swap` 函数，用来交换输入的元组。\n\n## 泛型约束\n\n在函数内部使用泛型变量的时候，由于事先不知道它是哪种类型，所以不能随意的操作它的属性或方法：\n\n```ts\nfunction loggingIdentity<T>(arg: T): T {\n    console.log(arg.length);\n    return arg;\n}\n\n// index.ts(2,19): error TS2339: Property 'length' does not exist on type 'T'.\n```\n\n上例中，泛型 `T` 不一定包含属性 `length`，所以编译的时候报错了。\n\n这时，我们可以对泛型进行约束，只允许这个函数传入那些包含 `length` 属性的变量。这就是泛型约束：\n\n```ts\ninterface Lengthwise {\n    length: number;\n}\n\nfunction loggingIdentity<T extends Lengthwise>(arg: T): T {\n    console.log(arg.length);\n    return arg;\n}\n```\n\n上例中，我们使用了 `extends` 约束了泛型 `T` 必须符合接口 `Lengthwise` 的形状，也就是必须包含 `length` 属性。\n\n此时如果调用 `loggingIdentity` 的时候，传入的 `arg` 不包含 `length`，那么在编译阶段就会报错了：\n\n```ts\ninterface Lengthwise {\n    length: number;\n}\n\nfunction loggingIdentity<T extends Lengthwise>(arg: T): T {\n    console.log(arg.length);\n    return arg;\n}\n\nloggingIdentity(7);\n\n// index.ts(10,17): error TS2345: Argument of type '7' is not assignable to parameter of type 'Lengthwise'.\n```\n\n多个类型参数之间也可以互相约束：\n\n```ts\nfunction copyFields<T extends U, U>(target: T, source: U): T {\n    for (let id in source) {\n        target[id] = (<T>source)[id];\n    }\n    return target;\n}\n\nlet x = { a: 1, b: 2, c: 3, d: 4 };\n\ncopyFields(x, { b: 10, d: 20 });\n```\n\n上例中，我们使用了两个类型参数，其中要求 `T` 继承 `U`，这样就保证了 `U` 上不会出现 `T` 中不存在的字段。\n\n## 泛型接口\n\n[之前学习过](../basics/type-of-function.md#接口中函数的定义)，可以使用接口的方式来定义一个函数需要符合的形状：\n\n```ts\ninterface SearchFunc {\n  (source: string, subString: string): boolean;\n}\n\nlet mySearch: SearchFunc;\nmySearch = function(source: string, subString: string) {\n    return source.search(subString) !== -1;\n}\n```\n\n当然也可以使用含有泛型的接口来定义函数的形状：\n\n```ts\ninterface CreateArrayFunc {\n    <T>(length: number, value: T): Array<T>;\n}\n\nlet createArray: CreateArrayFunc;\ncreateArray = function<T>(length: number, value: T): Array<T> {\n    let result: T[] = [];\n    for (let i = 0; i < length; i++) {\n        result[i] = value;\n    }\n    return result;\n}\n\ncreateArray(3, 'x'); // ['x', 'x', 'x']\n```\n\n进一步，我们可以把泛型参数提前到接口名上：\n\n```ts\ninterface CreateArrayFunc<T> {\n    (length: number, value: T): Array<T>;\n}\n\nlet createArray: CreateArrayFunc<any>;\ncreateArray = function<T>(length: number, value: T): Array<T> {\n    let result: T[] = [];\n    for (let i = 0; i < length; i++) {\n        result[i] = value;\n    }\n    return result;\n}\n\ncreateArray(3, 'x'); // ['x', 'x', 'x']\n```\n\n注意，此时在使用泛型接口的时候，需要定义泛型的类型。\n\n## 泛型类\n\n与泛型接口类似，泛型也可以用于类的类型定义中：\n\n```ts\nclass GenericNumber<T> {\n    zeroValue: T;\n    add: (x: T, y: T) => T;\n}\n\nlet myGenericNumber = new GenericNumber<number>();\nmyGenericNumber.zeroValue = 0;\nmyGenericNumber.add = function(x, y) { return x + y; };\n```\n\n## 泛型参数的默认类型\n\n在 TypeScript 2.3 以后，我们可以为泛型中的类型参数指定默认类型。当使用泛型时没有在代码中直接指定类型参数，从实际值参数中也无法推测出时，这个默认类型就会起作用。\n\n```ts\nfunction createArray<T = string>(length: number, value: T): Array<T> {\n    let result: T[] = [];\n    for (let i = 0; i < length; i++) {\n        result[i] = value;\n    }\n    return result;\n}\n```\n\n## 参考\n\n- [Generics](http://www.typescriptlang.org/docs/handbook/generics.html)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/generics.html)）\n- [Generic parameter defaults](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-3.html#generic-parameter-defaults)\n"
  },
  {
    "path": "advanced/string-literal-types.md",
    "content": "# 字符串字面量类型\n\n字符串字面量类型用来约束取值只能是某几个字符串中的一个。\n\n## 简单的例子\n\n```ts\ntype EventNames = 'click' | 'scroll' | 'mousemove';\nfunction handleEvent(ele: Element, event: EventNames) {\n    // do something\n}\n\nhandleEvent(document.getElementById('hello'), 'scroll');  // 没问题\nhandleEvent(document.getElementById('world'), 'dblclick'); // 报错，event 不能为 'dblclick'\n\n// index.ts(7,47): error TS2345: Argument of type '\"dblclick\"' is not assignable to parameter of type 'EventNames'.\n```\n\n上例中，我们使用 `type` 定了一个字符串字面量类型 `EventNames`，它只能取三种字符串中的一种。\n\n注意，**类型别名与字符串字面量类型都是使用 `type` 进行定义。**\n\n## 参考\n\n- [Advanced Types # Type Aliases](http://www.typescriptlang.org/docs/handbook/advanced-types.html#string-literal-types)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Advanced%20Types.html#字符串字面量类型)）\n"
  },
  {
    "path": "advanced/tuple.md",
    "content": "# 元组\n\n数组合并了相同类型的对象，而元组（Tuple）合并了不同类型的对象。\n\n元组起源于函数编程语言（如 F#），这些语言中会频繁使用元组。\n\n## 简单的例子\n\n定义一对值分别为 `string` 和 `number` 的元组：\n\n```ts\nlet tom: [string, number] = ['Tom', 25];\n```\n\n当赋值或访问一个已知索引的元素时，会得到正确的类型：\n\n```ts\nlet tom: [string, number];\ntom[0] = 'Tom';\ntom[1] = 25;\n\ntom[0].slice(1);\ntom[1].toFixed(2);\n```\n\n也可以只赋值其中一项：\n\n```ts\nlet tom: [string, number];\ntom[0] = 'Tom';\n```\n\n但是当直接对元组类型的变量进行初始化或者赋值的时候，需要提供所有元组类型中指定的项。\n\n```ts\nlet tom: [string, number];\ntom = ['Tom', 25];\n```\n\n```ts\nlet tom: [string, number];\ntom = ['Tom'];\n\n// Property '1' is missing in type '[string]' but required in type '[string, number]'.\n```\n\n## 越界的元素\n\n当添加越界的元素时，它的类型会被限制为元组中每个类型的联合类型：\n\n```ts\nlet tom: [string, number];\ntom = ['Tom', 25];\ntom.push('male');\ntom.push(true);\n\n// Argument of type 'true' is not assignable to parameter of type 'string | number'.\n```\n\n## 参考\n\n- [Basic Types # Tuple](http://www.typescriptlang.org/docs/handbook/basic-types.html#tuple)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Basic%20Types.html#元组-tuple)）\n"
  },
  {
    "path": "advanced/type-aliases.md",
    "content": "# 类型别名\n\n类型别名用来给一个类型起个新名字。\n\n## 简单的例子\n\n```ts\ntype Name = string;\ntype NameResolver = () => string;\ntype NameOrResolver = Name | NameResolver;\nfunction getName(n: NameOrResolver): Name {\n    if (typeof n === 'string') {\n        return n;\n    } else {\n        return n();\n    }\n}\n```\n\n上例中，我们使用 `type` 创建类型别名。\n\n类型别名常用于联合类型。\n\n## 参考\n\n- [Advanced Types # Type Aliases](http://www.typescriptlang.org/docs/handbook/advanced-types.html#type-aliases)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Advanced%20Types.html#类型别名)）\n"
  },
  {
    "path": "basics/README.md",
    "content": "# 基础\n\n本部分介绍了 TypeScript 中的常用类型和一些基本概念，旨在让大家对 TypeScript 有个初步的理解。具体内容包括：\n\n- [原始数据类型](primitive-data-types.md)\n- [任意值](any.md)\n- [类型推论](type-inference.md)\n- [联合类型](union-types.md)\n- [对象的类型——接口](type-of-object-interfaces.md)\n- [数组的类型](type-of-array.md)\n- [函数的类型](type-of-function.md)\n- [类型断言](type-assertion.md)\n- [声明文件](declaration-files.md)\n- [内置对象](built-in-objects.md)\n"
  },
  {
    "path": "basics/any.md",
    "content": "# 任意值\n\n任意值（Any）用来表示允许赋值为任意类型。\n\n## 什么是任意值类型\n\n如果是一个普通类型，在赋值过程中改变类型是不被允许的：\n\n```ts\nlet myFavoriteNumber: string = 'seven';\nmyFavoriteNumber = 7;\n\n// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.\n```\n\n但如果是 `any` 类型，则允许被赋值为任意类型。\n\n```ts\nlet myFavoriteNumber: any = 'seven';\nmyFavoriteNumber = 7;\n```\n\n## 任意值的属性和方法\n\n在任意值上访问任何属性都是允许的：\n\n```ts\nlet anyThing: any = 'hello';\nconsole.log(anyThing.myName);\nconsole.log(anyThing.myName.firstName);\n```\n\n也允许调用任何方法：\n\n```ts\nlet anyThing: any = 'Tom';\nanyThing.setName('Jerry');\nanyThing.setName('Jerry').sayHello();\nanyThing.myName.setFirstName('Cat');\n```\n\n可以认为，**声明一个变量为任意值之后，对它的任何操作，返回的内容的类型都是任意值**。\n\n## 未声明类型的变量\n\n**变量如果在声明的时候，未指定其类型，那么它会被识别为任意值类型**：\n\n```ts\nlet something;\nsomething = 'seven';\nsomething = 7;\n\nsomething.setName('Tom');\n```\n\n等价于\n\n```ts\nlet something: any;\nsomething = 'seven';\nsomething = 7;\n\nsomething.setName('Tom');\n```\n\n## 参考\n\n- [Basic Types # Any](http://www.typescriptlang.org/docs/handbook/basic-types.html#any)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Basic%20Types.html#任意值)）\n"
  },
  {
    "path": "basics/built-in-objects.md",
    "content": "# 内置对象\n\nJavaScript 中有很多[内置对象][]，它们可以直接在 TypeScript 中当做定义好了的类型。\n\n内置对象是指根据标准在全局作用域（Global）上存在的对象。这里的标准是指 ECMAScript 和其他环境（比如 DOM）的标准。\n\n## ECMAScript 的内置对象\n\nECMAScript 标准提供的内置对象有：\n\n`Boolean`、`Error`、`Date`、`RegExp` 等。\n\n我们可以在 TypeScript 中将变量定义为这些类型：\n\n```ts\nlet b: Boolean = new Boolean(1);\nlet e: Error = new Error('Error occurred');\nlet d: Date = new Date();\nlet r: RegExp = /[a-z]/;\n```\n\n更多的内置对象，可以查看 [MDN 的文档][内置对象]。\n\n而他们的定义文件，则在 [TypeScript 核心库的定义文件][]中。\n\n## DOM 和 BOM 的内置对象\n\nDOM 和 BOM 提供的内置对象有：\n\n`Document`、`HTMLElement`、`Event`、`NodeList` 等。\n\nTypeScript 中会经常用到这些类型：\n\n```ts\nlet body: HTMLElement = document.body;\nlet allDiv: NodeList = document.querySelectorAll('div');\ndocument.addEventListener('click', function(e: MouseEvent) {\n  // Do something\n});\n```\n\n它们的定义文件同样在 [TypeScript 核心库的定义文件][]中。\n\n## TypeScript 核心库的定义文件\n\n[TypeScript 核心库的定义文件][]中定义了所有浏览器环境需要用到的类型，并且是预置在 TypeScript 中的。\n\n当你在使用一些常用的方法的时候，TypeScript 实际上已经帮你做了很多类型判断的工作了，比如：\n\n```ts\nMath.pow(10, '2');\n\n// index.ts(1,14): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.\n```\n\n上面的例子中，`Math.pow` 必须接受两个 `number` 类型的参数。事实上 `Math.pow` 的类型定义如下：\n\n```ts\ninterface Math {\n    /**\n     * Returns the value of a base expression taken to a specified power.\n     * @param x The base value of the expression.\n     * @param y The exponent value of the expression.\n     */\n    pow(x: number, y: number): number;\n}\n```\n\n再举一个 DOM 中的例子：\n\n```ts\ndocument.addEventListener('click', function(e) {\n    console.log(e.targetCurrent);\n});\n\n// index.ts(2,17): error TS2339: Property 'targetCurrent' does not exist on type 'MouseEvent'.\n```\n\n上面的例子中，`addEventListener` 方法是在 TypeScript 核心库中定义的：\n\n```ts\ninterface Document extends Node, GlobalEventHandlers, NodeSelector, DocumentEvent {\n    addEventListener(type: string, listener: (ev: MouseEvent) => any, useCapture?: boolean): void;\n}\n```\n\n所以 `e` 被推断成了 `MouseEvent`，而 `MouseEvent` 是没有 `targetCurrent` 属性的，所以报错了。\n\n注意，TypeScript 核心库的定义中不包含 Node.js 部分。\n\n## 用 TypeScript 写 Node.js\n\nNode.js 不是内置对象的一部分，如果想用 TypeScript 写 Node.js，则需要引入第三方声明文件：\n\n```bash\nnpm install @types/node --save-dev\n```\n\n## 参考\n\n- [内置对象][]\n- [TypeScript 核心库的定义文件][]\n\n[内置对象]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects\n[TypeScript 核心库的定义文件]: https://github.com/Microsoft/TypeScript/tree/master/src/lib\n"
  },
  {
    "path": "basics/declaration-files.md",
    "content": "# 声明文件\n\n当使用第三方库时，我们需要引用它的声明文件，才能获得对应的代码补全、接口提示等功能。\n\n## 新语法索引\n\n由于本章涉及大量新语法，故在本章开头列出新语法的索引，方便大家在使用这些新语法时能快速查找到对应的讲解：\n\n- [`declare var`](#declare-var) 声明全局变量\n- [`declare function`](#declare-function) 声明全局方法\n- [`declare class`](#declare-class) 声明全局类\n- [`declare enum`](#declare-enum) 声明全局枚举类型\n- [`declare namespace`](#declare-namespace) 声明（含有子属性的）全局对象\n- [`interface` 和 `type`](#interface-和-type) 声明全局类型\n- [`export`](#export) 导出变量\n- [`export namespace`](#export-namespace) 导出（含有子属性的）对象\n- [`export default`](#export-default) ES6 默认导出\n- [`export =`](#export-1) commonjs 导出模块\n- [`export as namespace`](#export-as-namespace) UMD 库声明全局变量\n- [`declare global`](#declare-global) 扩展全局变量\n- [`declare module`](#declare-module) 扩展模块\n- [`/// <reference />`](#san-xie-xian-zhi-ling) 三斜线指令\n\n## 什么是声明语句\n\n假如我们想使用第三方库 jQuery，一种常见的方式是在 html 中通过 `<script>` 标签引入 jQuery，然后就可以使用全局变量 `$` 或 `jQuery` 了。\n\n我们通常这样获取一个 `id` 是 `foo` 的元素：\n\n```js\n$('#foo');\n// or\njQuery('#foo');\n```\n\n但是在 ts 中，编译器并不知道 `$` 或 `jQuery` 是什么东西[<sup>1</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/01-jquery)：\n\n```ts\njQuery('#foo');\n// ERROR: Cannot find name 'jQuery'.\n```\n\n这时，我们需要使用 `declare var` 来定义它的类型[<sup>2</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/02-declare-var)：\n\n```ts\ndeclare var jQuery: (selector: string) => any;\n\njQuery('#foo');\n```\n\n上例中，`declare var` 并没有真的定义一个变量，只是定义了全局变量 `jQuery` 的类型，仅仅会用于编译时的检查，在编译结果中会被删除。它编译结果是：\n\n```js\njQuery('#foo');\n```\n\n除了 `declare var` 之外，还有其他很多种声明语句，将会在后面详细介绍。\n\n## 什么是声明文件\n\n通常我们会把声明语句放到一个单独的文件（`jQuery.d.ts`）中，这就是声明文件[<sup>3</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/03-jquery-d-ts)：\n\n```ts\n// src/jQuery.d.ts\n\ndeclare var jQuery: (selector: string) => any;\n```\n\n```ts\n// src/index.ts\n\njQuery('#foo');\n```\n\n声明文件必需以 `.d.ts` 为后缀。\n\n一般来说，ts 会解析项目中所有的 `*.ts` 文件，当然也包含以 `.d.ts` 结尾的文件。所以当我们将 `jQuery.d.ts` 放到项目中时，其他所有 `*.ts` 文件就都可以获得 `jQuery` 的类型定义了。\n\n```plain\n/path/to/project\n├── src\n|  ├── index.ts\n|  └── jQuery.d.ts\n└── tsconfig.json\n```\n\n假如仍然无法解析，那么可以检查下 `tsconfig.json` 中的 `files`、`include` 和 `exclude` 配置，确保其包含了 `jQuery.d.ts` 文件。\n\n这里只演示了全局变量这种模式的声明文件，假如是通过模块导入的方式使用第三方库的话，那么引入声明文件又是另一种方式了，将会在后面详细介绍。\n\n### 第三方声明文件\n\n当然，jQuery 的声明文件不需要我们定义了，社区已经帮我们定义好了：[jQuery in DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/jquery/index.d.ts)。\n\n我们可以直接下载下来使用，但是更推荐的是使用 `@types` 统一管理第三方库的声明文件。\n\n`@types` 的使用方式很简单，直接用 npm 安装对应的声明模块即可，以 jQuery 举例：\n\n```bash\nnpm install @types/jquery --save-dev\n```\n\n可以在[这个页面](https://microsoft.github.io/TypeSearch/)搜索你需要的声明文件。\n\n## 书写声明文件\n\n当一个第三方库没有提供声明文件时，我们就需要自己书写声明文件了。前面只介绍了最简单的声明文件内容，而真正书写一个声明文件并不是一件简单的事，以下会详细介绍如何书写声明文件。\n\n在不同的场景下，声明文件的内容和使用方式会有所区别。\n\n库的使用场景主要有以下几种：\n\n- [全局变量](#quan-ju-bian-liang)：通过 `<script>` 标签引入第三方库，注入全局变量\n- [npm 包](#npm-bao)：通过 `import foo from 'foo'` 导入，符合 ES6 模块规范\n- [UMD 库](#umd-ku)：既可以通过 `<script>` 标签引入，又可以通过 `import` 导入\n- [直接扩展全局变量](#zhi-jie-kuo-zhan-quan-ju-bian-liang)：通过 `<script>` 标签引入后，改变一个全局变量的结构\n- [在 npm 包或 UMD 库中扩展全局变量](#zai-npm-bao-huo-umd-ku-zhong-kuo-zhan-quan-ju-bian-liang)：引用 npm 包或 UMD 库后，改变一个全局变量的结构\n- [模块插件](#mo-kuai-cha-jian)：通过 `<script>` 或 `import` 导入后，改变另一个模块的结构\n\n### 全局变量\n\n全局变量是最简单的一种场景，之前举的例子就是通过 `<script>` 标签引入 jQuery，注入全局变量 `$` 和 `jQuery`。\n\n使用全局变量的声明文件时，如果是以 `npm install @types/xxx --save-dev` 安装的，则不需要任何配置。如果是将声明文件直接存放于当前项目中，则建议和其他源码一起放到 `src` 目录下（或者对应的源码目录下）：\n\n```plain\n/path/to/project\n├── src\n|  ├── index.ts\n|  └── jQuery.d.ts\n└── tsconfig.json\n```\n\n如果没有生效，可以检查下 `tsconfig.json` 中的 `files`、`include` 和 `exclude` 配置，确保其包含了 `jQuery.d.ts` 文件。\n\n全局变量的声明文件主要有以下几种语法：\n\n- [`declare var`](#declare-var) 声明全局变量\n- [`declare function`](#declare-function) 声明全局方法\n- [`declare class`](#declare-class) 声明全局类\n- [`declare enum`](#declare-enum) 声明全局枚举类型\n- [`declare namespace`](#declare-namespace) 声明（含有子属性的）全局对象\n- [`interface` 和 `type`](#interface-he-type) 声明全局类型\n\n#### `declare var`\n\n在所有的声明语句中，`declare var` 是最简单的，如之前所学，它能够用来定义一个全局变量的类型。与其类似的，还有 `declare let` 和 `declare const`，使用 `let` 与使用 `var` 没有什么区别：\n\n```ts\n// src/jQuery.d.ts\n\ndeclare let jQuery: (selector: string) => any;\n```\n\n```ts\n// src/index.ts\n\njQuery('#foo');\n// 使用 declare let 定义的 jQuery 类型，允许修改这个全局变量\njQuery = function(selector) {\n    return document.querySelector(selector);\n};\n```\n\n而当我们使用 `const` 定义时，表示此时的全局变量是一个常量，不允许再去修改它的值了[<sup>4</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/04-declare-const-jquery)：\n\n```ts\n// src/jQuery.d.ts\n\ndeclare const jQuery: (selector: string) => any;\n\njQuery('#foo');\n// 使用 declare const 定义的 jQuery 类型，禁止修改这个全局变量\njQuery = function(selector) {\n    return document.querySelector(selector);\n};\n// ERROR: Cannot assign to 'jQuery' because it is a constant or a read-only property.\n```\n\n一般来说，全局变量都是禁止修改的常量，所以大部分情况都应该使用 `const` 而不是 `var` 或 `let`。\n\n需要注意的是，声明语句中只能定义类型，切勿在声明语句中定义具体的实现[<sup>5</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/05-declare-jquery-value)：\n\n```ts\ndeclare const jQuery = function(selector) {\n    return document.querySelector(selector);\n};\n// ERROR: An implementation cannot be declared in ambient contexts.\n```\n\n#### `declare function`\n\n`declare function` 用来定义全局函数的类型。jQuery 其实就是一个函数，所以也可以用 `function` 来定义：\n\n```ts\n// src/jQuery.d.ts\n\ndeclare function jQuery(selector: string): any;\n```\n\n```ts\n// src/index.ts\n\njQuery('#foo');\n```\n\n在函数类型的声明语句中，函数重载也是支持的[<sup>6</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/06-declare-function)：\n\n```ts\n// src/jQuery.d.ts\n\ndeclare function jQuery(selector: string): any;\ndeclare function jQuery(domReadyCallback: () => any): any;\n```\n\n```ts\n// src/index.ts\n\njQuery('#foo');\njQuery(function() {\n    alert('Dom Ready!');\n});\n```\n\n#### `declare class`\n\n当全局变量是一个类的时候，我们用 `declare class` 来定义它的类型[<sup>7</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/07-declare-class)：\n\n```ts\n// src/Animal.d.ts\n\ndeclare class Animal {\n    name: string;\n    constructor(name: string);\n    sayHi(): string;\n}\n```\n\n```ts\n// src/index.ts\n\nlet cat = new Animal('Tom');\n```\n\n同样的，`declare class` 语句也只能用来定义类型，不能用来定义具体的实现，比如定义 `sayHi` 方法的具体实现则会报错：\n\n```ts\n// src/Animal.d.ts\n\ndeclare class Animal {\n    name: string;\n    constructor(name: string);\n    sayHi() {\n        return `My name is ${this.name}`;\n    };\n    // ERROR: An implementation cannot be declared in ambient contexts.\n}\n```\n\n#### `declare enum`\n\n使用 `declare enum` 定义的枚举类型也称作外部枚举（Ambient Enums），举例如下[<sup>8</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/08-declare-enum)：\n\n```ts\n// src/Directions.d.ts\n\ndeclare enum Directions {\n    Up,\n    Down,\n    Left,\n    Right\n}\n```\n\n```ts\n// src/index.ts\n\nlet directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];\n```\n\n与其他全局变量的类型声明一致，`declare enum` 仅用来定义类型，而不是具体的值。\n\n`Directions.d.ts` 仅仅会用于编译时的检查，声明文件里的内容在编译结果中会被删除。它编译结果是：\n\n```js\nvar directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];\n```\n\n其中 `Directions` 是由第三方库定义好的全局变量。\n\n#### `declare namespace`\n\n`namespace` 是 ts 早期时为了解决模块化而创造的关键字，中文称为命名空间。\n\n由于历史遗留原因，在早期还没有 ES6 的时候，ts 提供了一种模块化方案，使用 `module` 关键字表示内部模块。但由于后来 ES6 也使用了 `module` 关键字，ts 为了兼容 ES6，使用 `namespace` 替代了自己的 `module`，更名为命名空间。\n\n随着 ES6 的广泛应用，现在已经不建议再使用 ts 中的 `namespace`，而推荐使用 ES6 的模块化方案了，故我们不再需要学习 `namespace` 的使用了。\n\n`namespace` 被淘汰了，但是在声明文件中，`declare namespace` 还是比较常用的，它用来表示全局变量是一个对象，包含很多子属性。\n\n比如 `jQuery` 是一个全局变量，它是一个对象，提供了一个 `jQuery.ajax` 方法可以调用，那么我们就应该使用 `declare namespace jQuery` 来声明这个拥有多个子属性的全局变量。\n\n```ts\n// src/jQuery.d.ts\n\ndeclare namespace jQuery {\n    function ajax(url: string, settings?: any): void;\n}\n```\n\n```ts\n// src/index.ts\n\njQuery.ajax('/api/get_something');\n```\n\n注意，在 `declare namespace` 内部，我们直接使用 `function ajax` 来声明函数，而不是使用 `declare function ajax`。类似的，也可以使用 `const`, `class`, `enum` 等语句[<sup>9</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/09-declare-namespace)：\n\n```ts\n// src/jQuery.d.ts\n\ndeclare namespace jQuery {\n    function ajax(url: string, settings?: any): void;\n    const version: number;\n    class Event {\n        blur(eventType: EventType): void\n    }\n    enum EventType {\n        CustomClick\n    }\n}\n```\n\n```ts\n// src/index.ts\n\njQuery.ajax('/api/get_something');\nconsole.log(jQuery.version);\nconst e = new jQuery.Event();\ne.blur(jQuery.EventType.CustomClick);\n```\n\n##### 嵌套的命名空间\n\n如果对象拥有深层的层级，则需要用嵌套的 `namespace` 来声明深层的属性的类型[<sup>10</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/10-declare-namespace-nesting)：\n\n```ts\n// src/jQuery.d.ts\n\ndeclare namespace jQuery {\n    function ajax(url: string, settings?: any): void;\n    namespace fn {\n        function extend(object: any): void;\n    }\n}\n```\n\n```ts\n// src/index.ts\n\njQuery.ajax('/api/get_something');\njQuery.fn.extend({\n    check: function() {\n        return this.each(function() {\n            this.checked = true;\n        });\n    }\n});\n```\n\n假如 `jQuery` 下仅有 `fn` 这一个属性（没有 `ajax` 等其他属性或方法），则可以不需要嵌套 `namespace`[<sup>11</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/11-declare-namespace-dot)：\n\n```ts\n// src/jQuery.d.ts\n\ndeclare namespace jQuery.fn {\n    function extend(object: any): void;\n}\n```\n\n```ts\n// src/index.ts\n\njQuery.fn.extend({\n    check: function() {\n        return this.each(function() {\n            this.checked = true;\n        });\n    }\n});\n```\n\n#### `interface` 和 `type`\n\n除了全局变量之外，可能有一些类型我们也希望能暴露出来。在类型声明文件中，我们可以直接使用 `interface` 或 `type` 来声明一个全局的接口或类型[<sup>12</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/12-interface)：\n\n```ts\n// src/jQuery.d.ts\n\ninterface AjaxSettings {\n    method?: 'GET' | 'POST'\n    data?: any;\n}\ndeclare namespace jQuery {\n    function ajax(url: string, settings?: AjaxSettings): void;\n}\n```\n\n这样的话，在其他文件中也可以使用这个接口或类型了：\n\n```ts\n// src/index.ts\n\nlet settings: AjaxSettings = {\n    method: 'POST',\n    data: {\n        name: 'foo'\n    }\n};\njQuery.ajax('/api/post_something', settings);\n```\n\n`type` 与 `interface` 类似，不再赘述。\n\n##### 防止命名冲突\n\n暴露在最外层的 `interface` 或 `type` 会作为全局类型作用于整个项目中，我们应该尽可能的减少全局变量或全局类型的数量。故最好将他们放到 `namespace` 下[<sup>13</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/13-avoid-name-conflict)：\n\n```ts\n// src/jQuery.d.ts\n\ndeclare namespace jQuery {\n    interface AjaxSettings {\n        method?: 'GET' | 'POST'\n        data?: any;\n    }\n    function ajax(url: string, settings?: AjaxSettings): void;\n}\n```\n\n注意，在使用这个 `interface` 的时候，也应该加上 `jQuery` 前缀：\n\n```ts\n// src/index.ts\n\nlet settings: jQuery.AjaxSettings = {\n    method: 'POST',\n    data: {\n        name: 'foo'\n    }\n};\njQuery.ajax('/api/post_something', settings);\n```\n\n#### 声明合并\n\n假如 jQuery 既是一个函数，可以直接被调用 `jQuery('#foo')`，又是一个对象，拥有子属性 `jQuery.ajax()`（事实确实如此），那么我们可以组合多个声明语句，它们会不冲突的合并起来[<sup>14</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/14-declaration-merging)：\n\n```ts\n// src/jQuery.d.ts\n\ndeclare function jQuery(selector: string): any;\ndeclare namespace jQuery {\n    function ajax(url: string, settings?: any): void;\n}\n```\n\n```ts\n// src/index.ts\n\njQuery('#foo');\njQuery.ajax('/api/get_something');\n```\n\n关于声明合并的更多用法，可以查看[声明合并](../advanced/declaration-merging.md)章节。\n\n### npm 包\n\n一般我们通过 `import foo from 'foo'` 导入一个 npm 包，这是符合 ES6 模块规范的。\n\n在我们尝试给一个 npm 包创建声明文件之前，需要先看看它的声明文件是否已经存在。一般来说，npm 包的声明文件可能存在于两个地方：\n\n1. 与该 npm 包绑定在一起。判断依据是 `package.json` 中有 `types` 字段，或者有一个 `index.d.ts` 声明文件。这种模式不需要额外安装其他包，是最为推荐的，所以以后我们自己创建 npm 包的时候，最好也将声明文件与 npm 包绑定在一起。\n2. 发布到 `@types` 里。我们只需要尝试安装一下对应的 `@types` 包就知道是否存在该声明文件，安装命令是 `npm install @types/foo --save-dev`。这种模式一般是由于 npm 包的维护者没有提供声明文件，所以只能由其他人将声明文件发布到 `@types` 里了。\n\n假如以上两种方式都没有找到对应的声明文件，那么我们就需要自己为它写声明文件了。由于是通过 `import` 语句导入的模块，所以声明文件存放的位置也有所约束，一般有两种方案：\n\n1. 创建一个 `node_modules/@types/foo/index.d.ts` 文件，存放 `foo` 模块的声明文件。这种方式不需要额外的配置，但是 `node_modules` 目录不稳定，代码也没有被保存到仓库中，无法回溯版本，有不小心被删除的风险，故不太建议用这种方案，一般只用作临时测试。\n2. 创建一个 `types` 目录，专门用来管理自己写的声明文件，将 `foo` 的声明文件放到 `types/foo/index.d.ts` 中。这种方式需要配置下 `tsconfig.json` 中的 `paths` 和 `baseUrl` 字段。\n\n目录结构：\n\n```plain\n/path/to/project\n├── src\n|  └── index.ts\n├── types\n|  └── foo\n|     └── index.d.ts\n└── tsconfig.json\n```\n\n`tsconfig.json` 内容：\n\n```json\n{\n    \"compilerOptions\": {\n        \"module\": \"commonjs\",\n        \"baseUrl\": \"./\",\n        \"paths\": {\n            \"*\": [\"types/*\"]\n        }\n    }\n}\n```\n\n如此配置之后，通过 `import` 导入 `foo` 的时候，也会去 `types` 目录下寻找对应的模块的声明文件了。\n\n注意 `module` 配置可以有很多种选项，不同的选项会影响模块的导入导出模式。这里我们使用了 `commonjs` 这个最常用的选项，后面的教程也都默认使用的这个选项。\n\n不管采用了以上两种方式中的哪一种，我都**强烈建议**大家将书写好的声明文件（通过给第三方库发 pull request，或者直接提交到 `@types` 里）发布到开源社区中，享受了这么多社区的优秀的资源，就应该在力所能及的时候给出一些回馈。只有所有人都参与进来，才能让 ts 社区更加繁荣。\n\nnpm 包的声明文件主要有以下几种语法：\n\n- [`export`](#export) 导出变量\n- [`export namespace`](#export-namespace) 导出（含有子属性的）对象\n- [`export default`](#export-default) ES6 默认导出\n- [`export =`](#export-1) commonjs 导出模块\n\n#### `export`\n\nnpm 包的声明文件与全局变量的声明文件有很大区别。在 npm 包的声明文件中，使用 `declare` 不再会声明一个全局变量，而只会在当前文件中声明一个局部变量。只有在声明文件中使用 `export` 导出，然后在使用方 `import` 导入后，才会应用到这些类型声明。\n\n`export` 的语法与普通的 ts 中的语法类似，区别仅在于声明文件中禁止定义具体的实现[<sup>15</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/15-export)：\n\n```ts\n// types/foo/index.d.ts\n\nexport const name: string;\nexport function getName(): string;\nexport class Animal {\n    constructor(name: string);\n    sayHi(): string;\n}\nexport enum Directions {\n    Up,\n    Down,\n    Left,\n    Right\n}\nexport interface Options {\n    data: any;\n}\n```\n\n对应的导入和使用模块应该是这样：\n\n```ts\n// src/index.ts\n\nimport { name, getName, Animal, Directions, Options } from 'foo';\n\nconsole.log(name);\nlet myName = getName();\nlet cat = new Animal('Tom');\nlet directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];\nlet options: Options = {\n    data: {\n        name: 'foo'\n    }\n};\n```\n\n##### 混用 `declare` 和 `export`\n\n我们也可以使用 `declare` 先声明多个变量，最后再用 `export` 一次性导出。上例的声明文件可以等价的改写为[<sup>16</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/16-declare-and-export)：\n\n```ts\n// types/foo/index.d.ts\n\ndeclare const name: string;\ndeclare function getName(): string;\ndeclare class Animal {\n    constructor(name: string);\n    sayHi(): string;\n}\ndeclare enum Directions {\n    Up,\n    Down,\n    Left,\n    Right\n}\ninterface Options {\n    data: any;\n}\n\nexport { name, getName, Animal, Directions, Options };\n```\n\n注意，与全局变量的声明文件类似，`interface` 前是不需要 `declare` 的。\n\n#### `export namespace`\n\n与 `declare namespace` 类似，`export namespace` 用来导出一个拥有子属性的对象[<sup>17</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/17-export-namespace)：\n\n```ts\n// types/foo/index.d.ts\n\nexport namespace foo {\n    const name: string;\n    namespace bar {\n        function baz(): string;\n    }\n}\n```\n\n```ts\n// src/index.ts\n\nimport { foo } from 'foo';\n\nconsole.log(foo.name);\nfoo.bar.baz();\n```\n\n#### `export default`\n\n在 ES6 模块系统中，使用 `export default` 可以导出一个默认值，使用方可以用 `import foo from 'foo'` 而不是 `import { foo } from 'foo'` 来导入这个默认值。\n\n在类型声明文件中，`export default` 用来导出默认值的类型[<sup>18</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/18-export-default)：\n\n```ts\n// types/foo/index.d.ts\n\nexport default function foo(): string;\n```\n\n```ts\n// src/index.ts\n\nimport foo from 'foo';\n\nfoo();\n```\n\n注意，只有 `function`、`class` 和 `interface` 可以直接默认导出，其他的变量需要先定义出来，再默认导出[<sup>19</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/19-export-default-enum-error)：\n\n```ts\n// types/foo/index.d.ts\n\nexport default enum Directions {\n// ERROR: Expression expected.\n    Up,\n    Down,\n    Left,\n    Right\n}\n```\n\n上例中 `export default enum` 是错误的语法，需要使用 `declare enum` 定义出来，然后使用 `export default` 导出：\n\n```ts\n// types/foo/index.d.ts\n\ndeclare enum Directions {\n    Up,\n    Down,\n    Left,\n    Right\n}\n\nexport default Directions;\n```\n\n针对这种默认导出，我们一般会将导出语句放在整个声明文件的最前面[<sup>20</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/20-export-default-enum)：\n\n```ts\n// types/foo/index.d.ts\n\nexport default Directions;\n\ndeclare enum Directions {\n    Up,\n    Down,\n    Left,\n    Right\n}\n```\n\n#### `export =`\n\n在 commonjs 规范中，我们用以下方式来导出一个模块：\n\n```js\n// 整体导出\nmodule.exports = foo;\n// 单个导出\nexports.bar = bar;\n```\n\n在 ts 中，针对这种模块导出，有多种方式可以导入，第一种方式是 `const ... = require`：\n\n```js\n// 整体导入\nconst foo = require('foo');\n// 单个导入\nconst bar = require('foo').bar;\n```\n\n第二种方式是 `import ... from`，注意针对整体导出，需要使用 `import * as` 来导入：\n\n```ts\n// 整体导入\nimport * as foo from 'foo';\n// 单个导入\nimport { bar } from 'foo';\n```\n\n第三种方式是 `import ... require`，这也是 ts 官方推荐的方式：\n\n```ts\n// 整体导入\nimport foo = require('foo');\n// 单个导入\nimport bar = require('foo').bar;\n```\n\n对于这种使用 commonjs 规范的库，假如要为它写类型声明文件的话，就需要使用到 `export =` 这种语法了[<sup>21</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/21-export-equal)：\n\n```ts\n// types/foo/index.d.ts\n\nexport = foo;\n\ndeclare function foo(): string;\ndeclare namespace foo {\n    const bar: number;\n}\n```\n\n需要注意的是，上例中使用了 `export =` 之后，就不能再单个导出 `export { bar }` 了。所以我们通过声明合并，使用 `declare namespace foo` 来将 `bar` 合并到 `foo` 里。\n\n准确地讲，`export =` 不仅可以用在声明文件中，也可以用在普通的 ts 文件中。实际上，`import ... require` 和 `export =` 都是 ts 为了兼容 AMD 规范和 commonjs 规范而创立的新语法，由于并不常用也不推荐使用，所以这里就不详细介绍了，感兴趣的可以看[官方文档](https://www.typescriptlang.org/docs/handbook/modules.html#export--and-import--require)。\n\n由于很多第三方库是 commonjs 规范的，所以声明文件也就不得不用到 `export =` 这种语法了。但是还是需要再强调下，相比与 `export =`，我们更推荐使用 ES6 标准的 `export default` 和 `export`。\n\n### UMD 库\n\n既可以通过 `<script>` 标签引入，又可以通过 `import` 导入的库，称为 UMD 库。相比于 npm 包的类型声明文件，我们需要额外声明一个全局变量，为了实现这种方式，ts 提供了一个新语法 `export as namespace`。\n\n#### `export as namespace`\n\n一般使用 `export as namespace` 时，都是先有了 npm 包的声明文件，再基于它添加一条 `export as namespace` 语句，即可将声明好的一个变量声明为全局变量，举例如下[<sup>22</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/22-export-as-namespace)：\n\n```ts\n// types/foo/index.d.ts\n\nexport as namespace foo;\nexport = foo;\n\ndeclare function foo(): string;\ndeclare namespace foo {\n    const bar: number;\n}\n```\n\n当然它也可以与 `export default` 一起使用：\n\n```ts\n// types/foo/index.d.ts\n\nexport as namespace foo;\nexport default foo;\n\ndeclare function foo(): string;\ndeclare namespace foo {\n    const bar: number;\n}\n```\n\n### 直接扩展全局变量\n\n有的第三方库扩展了一个全局变量，可是此全局变量的类型却没有相应的更新过来，就会导致 ts 编译错误，此时就需要扩展全局变量的类型。比如扩展 `String` 类型[<sup>23</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/23-merge-global-interface)：\n\n```ts\ninterface String {\n    prependHello(): string;\n}\n\n'foo'.prependHello();\n```\n\n通过声明合并，使用 `interface String` 即可给 `String` 添加属性或方法。\n\n也可以使用 `declare namespace` 给已有的命名空间添加类型声明[<sup>24</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/24-merge-global-namespace)：\n\n```ts\n// types/jquery-plugin/index.d.ts\n\ndeclare namespace JQuery {\n    interface CustomOptions {\n        bar: string;\n    }\n}\n\ninterface JQueryStatic {\n    foo(options: JQuery.CustomOptions): string;\n}\n```\n\n```ts\n// src/index.ts\n\njQuery.foo({\n    bar: ''\n});\n```\n\n### 在 npm 包或 UMD 库中扩展全局变量\n\n如之前所说，对于一个 npm 包或者 UMD 库的声明文件，只有 `export` 导出的类型声明才能被导入。所以对于 npm 包或 UMD 库，如果导入此库之后会扩展全局变量，则需要使用另一种语法在声明文件中扩展全局变量的类型，那就是 `declare global`。\n\n#### `declare global`\n\n使用 `declare global` 可以在 npm 包或者 UMD 库的声明文件中扩展全局变量的类型[<sup>25</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/25-declare-global)：\n\n```ts\n// types/foo/index.d.ts\n\ndeclare global {\n    interface String {\n        prependHello(): string;\n    }\n}\n\nexport {};\n```\n\n```ts\n// src/index.ts\n\n'bar'.prependHello();\n```\n\n注意即使此声明文件不需要导出任何东西，仍然需要导出一个空对象，用来告诉编译器这是一个模块的声明文件，而不是一个全局变量的声明文件。\n\n### 模块插件\n\n有时通过 `import` 导入一个模块插件，可以改变另一个原有模块的结构。此时如果原有模块已经有了类型声明文件，而插件模块没有类型声明文件，就会导致类型不完整，缺少插件部分的类型。ts 提供了一个语法 `declare module`，它可以用来扩展原有模块的类型。\n\n#### `declare module`\n\n如果是需要扩展原有模块的话，需要在类型声明文件中先引用原有模块，再使用 `declare module` 扩展原有模块[<sup>26</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/26-declare-module)：\n\n```ts\n// types/moment-plugin/index.d.ts\n\nimport * as moment from 'moment';\n\ndeclare module 'moment' {\n    export function foo(): moment.CalendarKey;\n}\n```\n\n```ts\n// src/index.ts\n\nimport * as moment from 'moment';\nimport 'moment-plugin';\n\nmoment.foo();\n```\n\n`declare module` 也可用于在一个文件中一次性声明多个模块的类型[<sup>27</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/27-multiple-declare-module)：\n\n```ts\n// types/foo-bar.d.ts\n\ndeclare module 'foo' {\n    export interface Foo {\n        foo: string;\n    }\n}\n\ndeclare module 'bar' {\n    export function bar(): string;\n}\n```\n\n```ts\n// src/index.ts\n\nimport { Foo } from 'foo';\nimport * as bar from 'bar';\n\nlet f: Foo;\nbar.bar();\n```\n\n### 声明文件中的依赖\n\n一个声明文件有时会依赖另一个声明文件中的类型，比如在前面的 `declare module` 的例子中，我们就在声明文件中导入了 `moment`，并且使用了 `moment.CalendarKey` 这个类型：\n\n```ts\n// types/moment-plugin/index.d.ts\n\nimport * as moment from 'moment';\n\ndeclare module 'moment' {\n    export function foo(): moment.CalendarKey;\n}\n```\n\n除了可以在声明文件中通过 `import` 导入另一个声明文件中的类型之外，还有一个语法也可以用来导入另一个声明文件，那就是三斜线指令。\n\n#### 三斜线指令\n\n与 `namespace` 类似，三斜线指令也是 ts 在早期版本中为了描述模块之间的依赖关系而创造的语法。随着 ES6 的广泛应用，现在已经不建议再使用 ts 中的三斜线指令来声明模块之间的依赖关系了。\n\n但是在声明文件中，它还是有一定的用武之地。\n\n类似于声明文件中的 `import`，它可以用来导入另一个声明文件。与 `import` 的区别是，当且仅当在以下几个场景下，我们才需要使用三斜线指令替代 `import`：\n\n- 当我们在**书写**一个全局变量的声明文件时\n- 当我们需要**依赖**一个全局变量的声明文件时\n\n##### **书写**一个全局变量的声明文件\n\n这些场景听上去很拗口，但实际上很好理解——在全局变量的声明文件中，是不允许出现 `import`, `export` 关键字的。一旦出现了，那么他就会被视为一个 npm 包或 UMD 库，就不再是全局变量的声明文件了。故当我们在书写一个全局变量的声明文件时，如果需要引用另一个库的类型，那么就必须用三斜线指令了[<sup>28</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/28-triple-slash-directives)：\n\n```ts\n// types/jquery-plugin/index.d.ts\n\n/// <reference types=\"jquery\" />\n\ndeclare function foo(options: JQuery.AjaxSettings): string;\n```\n\n```ts\n// src/index.ts\n\nfoo({});\n```\n\n三斜线指令的语法如上，`///` 后面使用 xml 的格式添加了对 `jquery` 类型的依赖，这样就可以在声明文件中使用 `JQuery.AjaxSettings` 类型了。\n\n注意，三斜线指令必须放在文件的最顶端，三斜线指令的前面只允许出现单行或多行注释。\n\n##### **依赖**一个全局变量的声明文件\n\n在另一个场景下，当我们需要依赖一个全局变量的声明文件时，由于全局变量不支持通过 `import` 导入，当然也就必须使用三斜线指令来引入了[<sup>29</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/29-triple-slash-directives-global)：\n\n```ts\n// types/node-plugin/index.d.ts\n\n/// <reference types=\"node\" />\n\nexport function foo(p: NodeJS.Process): string;\n```\n\n```ts\n// src/index.ts\n\nimport { foo } from 'node-plugin';\n\nfoo(global.process);\n```\n\n在上面的例子中，我们通过三斜线指引入了 `node` 的类型，然后在声明文件中使用了 `NodeJS.Process` 这个类型。最后在使用到 `foo` 的时候，传入了 `node` 中的全局变量 `process`。\n\n由于引入的 `node` 中的类型都是全局变量的类型，它们是没有办法通过 `import` 来导入的，所以这种场景下也只能通过三斜线指令来引入了。\n\n以上两种使用场景下，都是由于需要书写或需要依赖全局变量的声明文件，所以必须使用三斜线指令。在其他的一些不是必要使用三斜线指令的情况下，就都需要使用 `import` 来导入。\n\n##### 拆分声明文件\n\n当我们的全局变量的声明文件太大时，可以通过拆分为多个文件，然后在一个入口文件中将它们一一引入，来提高代码的可维护性。比如 `jQuery` 的声明文件就是这样的：\n\n```ts\n// node_modules/@types/jquery/index.d.ts\n\n/// <reference types=\"sizzle\" />\n/// <reference path=\"JQueryStatic.d.ts\" />\n/// <reference path=\"JQuery.d.ts\" />\n/// <reference path=\"misc.d.ts\" />\n/// <reference path=\"legacy.d.ts\" />\n\nexport = jQuery;\n```\n\n其中用到了 `types` 和 `path` 两种不同的指令。它们的区别是：`types` 用于声明对另一个库的依赖，而 `path` 用于声明对另一个文件的依赖。\n\n上例中，`sizzle` 是与 `jquery` 平行的另一个库，所以需要使用 `types=\"sizzle\"` 来声明对它的依赖。而其他的三斜线指令就是将 `jquery` 的声明拆分到不同的文件中了，然后在这个入口文件中使用 `path=\"foo\"` 将它们一一引入。\n\n##### 其他三斜线指令\n\n除了这两种三斜线指令之外，还有其他的三斜线指令，比如 `/// <reference no-default-lib=\"true\"/>`, `/// <amd-module />` 等，但它们都是废弃的语法，故这里就不介绍了，详情可见[官网](http://www.typescriptlang.org/docs/handbook/triple-slash-directives.html)。\n\n### 自动生成声明文件\n\n如果库的源码本身就是由 ts 写的，那么在使用 `tsc` 脚本将 ts 编译为 js 的时候，添加 `declaration` 选项，就可以同时也生成 `.d.ts` 声明文件了。\n\n我们可以在命令行中添加 `--declaration`（简写 `-d`），或者在 `tsconfig.json` 中添加 `declaration` 选项。这里以 `tsconfig.json` 为例：\n\n```json\n{\n    \"compilerOptions\": {\n        \"module\": \"commonjs\",\n        \"outDir\": \"lib\",\n        \"declaration\": true,\n    }\n}\n```\n\n上例中我们添加了 `outDir` 选项，将 ts 文件的编译结果输出到 `lib` 目录下，然后添加了 `declaration` 选项，设置为 `true`，表示将会由 ts 文件自动生成 `.d.ts` 声明文件，也会输出到 `lib` 目录下。\n\n运行 `tsc` 之后，目录结构如下[<sup>30</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/30-auto-d-ts)：\n\n```plain\n/path/to/project\n├── lib\n|  ├── bar\n|  |  ├── index.d.ts\n|  |  └── index.js\n|  ├── index.d.ts\n|  └── index.js\n├── src\n|  ├── bar\n|  |  └── index.ts\n|  └── index.ts\n├── package.json\n└── tsconfig.json\n```\n\n在这个例子中，`src` 目录下有两个 ts 文件，分别是 `src/index.ts` 和 `src/bar/index.ts`，它们被编译到 `lib` 目录下的同时，也会生成对应的两个声明文件 `lib/index.d.ts` 和 `lib/bar/index.d.ts`。它们的内容分别是：\n\n```ts\n// src/index.ts\n\nexport * from './bar';\n\nexport default function foo() {\n    return 'foo';\n}\n```\n\n```ts\n// src/bar/index.ts\n\nexport function bar() {\n    return 'bar';\n}\n```\n\n```ts\n// lib/index.d.ts\n\nexport * from './bar';\nexport default function foo(): string;\n```\n\n```ts\n// lib/bar/index.d.ts\n\nexport declare function bar(): string;\n```\n\n可见，自动生成的声明文件基本保持了源码的结构，而将具体实现去掉了，生成了对应的类型声明。\n\n使用 `tsc` 自动生成声明文件时，每个 ts 文件都会对应一个 `.d.ts` 声明文件。这样的好处是，使用方不仅可以在使用 `import foo from 'foo'` 导入默认的模块时获得类型提示，还可以在使用 `import bar from 'foo/lib/bar'` 导入一个子模块时，也获得对应的类型提示。\n\n除了 `declaration` 选项之外，还有几个选项也与自动生成声明文件有关，这里只简单列举出来，不做详细演示了：\n\n- `declarationDir` 设置生成 `.d.ts` 文件的目录\n- `declarationMap` 对每个 `.d.ts` 文件，都生成对应的 `.d.ts.map`（sourcemap）文件\n- `emitDeclarationOnly` 仅生成 `.d.ts` 文件，不生成 `.js` 文件\n\n## 发布声明文件\n\n当我们为一个库写好了声明文件之后，下一步就是将它发布出去了。\n\n此时有两种方案：\n\n1. 将声明文件和源码放在一起\n2. 将声明文件发布到 `@types` 下\n\n这两种方案中优先选择第一种方案。保持声明文件与源码在一起，使用时就不需要额外增加单独的声明文件库的依赖了，而且也能保证声明文件的版本与源码的版本保持一致。\n\n仅当我们在给别人的仓库添加类型声明文件，但原作者不愿意合并 pull request 时，才需要使用第二种方案，将声明文件发布到 `@types` 下。\n\n### 将声明文件和源码放在一起\n\n如果声明文件是通过 `tsc` 自动生成的，那么无需做任何其他配置，只需要把编译好的文件也发布到 npm 上，使用方就可以获取到类型提示了。\n\n如果是手动写的声明文件，那么需要满足以下条件之一，才能被正确的识别：\n\n- 给 `package.json` 中的 `types` 或 `typings` 字段指定一个类型声明文件地址\n- 在项目根目录下，编写一个 `index.d.ts` 文件\n- 针对入口文件（`package.json` 中的 `main` 字段指定的入口文件），编写一个同名不同后缀的 `.d.ts` 文件\n\n第一种方式是给 `package.json` 中的 `types` 或 `typings` 字段指定一个类型声明文件地址。比如：\n\n```json\n{\n    \"name\": \"foo\",\n    \"version\": \"1.0.0\",\n    \"main\": \"lib/index.js\",\n    \"types\": \"foo.d.ts\",\n}\n```\n\n指定了 `types` 为 `foo.d.ts` 之后，导入此库的时候，就会去找 `foo.d.ts` 作为此库的类型声明文件了。\n\n`typings` 与 `types` 一样，只是另一种写法。\n\n如果没有指定 `types` 或 `typings`，那么就会在根目录下寻找 `index.d.ts` 文件，将它视为此库的类型声明文件。\n\n如果没有找到 `index.d.ts` 文件，那么就会寻找入口文件（`package.json` 中的 `main` 字段指定的入口文件）是否存在对应同名不同后缀的 `.d.ts` 文件。\n\n比如 `package.json` 是这样时：\n\n```json\n{\n    \"name\": \"foo\",\n    \"version\": \"1.0.0\",\n    \"main\": \"lib/index.js\"\n}\n```\n\n就会先识别 `package.json` 中是否存在 `types` 或 `typings` 字段。发现不存在，那么就会寻找是否存在 `index.d.ts` 文件。如果还是不存在，那么就会寻找是否存在 `lib/index.d.ts` 文件。假如说连 `lib/index.d.ts` 都不存在的话，就会被认为是一个没有提供类型声明文件的库了。\n\n有的库为了支持导入子模块，比如 `import bar from 'foo/lib/bar'`，就需要额外再编写一个类型声明文件 `lib/bar.d.ts` 或者 `lib/bar/index.d.ts`，这与自动生成声明文件类似，一个库中同时包含了多个类型声明文件。\n\n### 将声明文件发布到 `@types` 下\n\n如果我们是在给别人的仓库添加类型声明文件，但原作者不愿意合并 pull request，那么就需要将声明文件发布到 `@types` 下。\n\n与普通的 npm 模块不同，`@types` 是统一由 [DefinitelyTyped][] 管理的。要将声明文件发布到 `@types` 下，就需要给 [DefinitelyTyped][] 创建一个 pull-request，其中包含了类型声明文件，测试代码，以及 `tsconfig.json` 等。\n\npull-request 需要符合它们的规范，并且通过测试，才能被合并，稍后就会被自动发布到 `@types` 下。\n\n在 [DefinitelyTyped][] 中创建一个新的类型声明，需要用到一些工具，[DefinitelyTyped][] 的文档中已经有了[详细的介绍](https://github.com/DefinitelyTyped/DefinitelyTyped#create-a-new-package)，这里就不赘述了，以官方文档为准。\n\n如果大家有此类需求，可以参考下笔者[提交的 pull-request](https://github.com/DefinitelyTyped/DefinitelyTyped/pull/30336/files) 。\n\n## 参考\n\n- [Writing Declaration Files](http://www.typescriptlang.org/docs/handbook/writing-declaration-files.html)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/declaration%20files/Introduction.html)）\n- [Triple-Slash Directives](http://www.typescriptlang.org/docs/handbook/triple-slash-directives.html)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Triple-Slash%20Directives.html)）\n- [typeRoots or paths](https://github.com/Microsoft/TypeScript/issues/22217#issuecomment-369783776)\n- [DefinitelyTyped][]\n\n[DefinitelyTyped]: https://github.com/DefinitelyTyped/DefinitelyTyped/\n"
  },
  {
    "path": "basics/primitive-data-types.md",
    "content": "# 原始数据类型\n\nJavaScript 的类型分为两种：原始数据类型（[Primitive data types][]）和对象类型（Object types）。\n\n原始数据类型包括：布尔值、数值、字符串、`null`、`undefined` 以及 ES6 中的新类型 [`Symbol`][] 和 ES10 中的新类型 [`BigInt`][]。\n\n本节主要介绍**前五种**原始数据类型在 TypeScript 中的应用。\n\n## 布尔值\n\n布尔值是最基础的数据类型，在 TypeScript 中，使用 `boolean` 定义布尔值类型：\n\n```ts\nlet isDone: boolean = false;\n\n// 编译通过\n// 后面约定，未强调编译错误的代码片段，默认为编译通过\n```\n\n注意，使用构造函数 `Boolean` 创造的对象**不是**布尔值：\n\n```ts\nlet createdByNewBoolean: boolean = new Boolean(1);\n\n// Type 'Boolean' is not assignable to type 'boolean'.\n//   'boolean' is a primitive, but 'Boolean' is a wrapper object. Prefer using 'boolean' when possible.\n```\n\n事实上 `new Boolean()` 返回的是一个 `Boolean` 对象：\n\n```ts\nlet createdByNewBoolean: Boolean = new Boolean(1);\n```\n\n直接调用 `Boolean` 也可以返回一个 `boolean` 类型：\n\n```ts\nlet createdByBoolean: boolean = Boolean(1);\n```\n\n在 TypeScript 中，`boolean` 是 JavaScript 中的基本类型，而 `Boolean` 是 JavaScript 中的构造函数。其他基本类型（除了 `null` 和 `undefined`）一样，不再赘述。\n\n## 数值\n\n使用 `number` 定义数值类型：\n\n```ts\nlet decLiteral: number = 6;\nlet hexLiteral: number = 0xf00d;\n// ES6 中的二进制表示法\nlet binaryLiteral: number = 0b1010;\n// ES6 中的八进制表示法\nlet octalLiteral: number = 0o744;\nlet notANumber: number = NaN;\nlet infinityNumber: number = Infinity;\n```\n\n编译结果：\n\n```js\nvar decLiteral = 6;\nvar hexLiteral = 0xf00d;\n// ES6 中的二进制表示法\nvar binaryLiteral = 10;\n// ES6 中的八进制表示法\nvar octalLiteral = 484;\nvar notANumber = NaN;\nvar infinityNumber = Infinity;\n```\n\n其中 `0b1010` 和 `0o744` 是 [ES6 中的二进制和八进制表示法][]，它们会被编译为十进制数字。\n\n## 字符串\n\n使用 `string` 定义字符串类型：\n\n```ts\nlet myName: string = 'Tom';\nlet myAge: number = 25;\n\n// 模板字符串\nlet sentence: string = `Hello, my name is ${myName}.\nI'll be ${myAge + 1} years old next month.`;\n```\n\n编译结果：\n\n```js\nvar myName = 'Tom';\nvar myAge = 25;\n// 模板字符串\nvar sentence = \"Hello, my name is \" + myName + \".\\nI'll be \" + (myAge + 1) + \" years old next month.\";\n```\n\n其中 <code>&#96;</code> 用来定义 [ES6 中的模板字符串][]，`${expr}` 用来在模板字符串中嵌入表达式。\n\n## 空值\n\nJavaScript 没有空值（Void）的概念，在 TypeScript 中，可以用 `void` 表示没有任何返回值的函数：\n\n```ts\nfunction alertName(): void {\n    alert('My name is Tom');\n}\n```\n\n声明一个 `void` 类型的变量没有什么用，因为你只能将它赋值为 `undefined` 和 `null`（只在 --strictNullChecks 未指定时）：\n\n```ts\nlet unusable: void = undefined;\n```\n\n## Null 和 Undefined\n\n在 TypeScript 中，可以使用 `null` 和 `undefined` 来定义这两个原始数据类型：\n\n```ts\nlet u: undefined = undefined;\nlet n: null = null;\n```\n\n与 `void` 的区别是，`undefined` 和 `null` 是所有类型的子类型。也就是说 `undefined` 类型的变量，可以赋值给 `number` 类型的变量：\n\n```ts\n// 这样不会报错\nlet num: number = undefined;\n```\n\n```ts\n// 这样也不会报错\nlet u: undefined;\nlet num: number = u;\n```\n\n而 `void` 类型的变量不能赋值给 `number` 类型的变量：\n\n```ts\nlet u: void;\nlet num: number = u;\n\n// Type 'void' is not assignable to type 'number'.\n```\n\n## 参考\n\n- [Basic Types](http://www.typescriptlang.org/docs/handbook/basic-types.html)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Basic%20Types.html)）\n- [Primitive data types][]\n- [ES6 中的新类型 `Symbol`][]\n- [ES6 中的二进制和八进制表示法][]\n- [ES6 中的模板字符串][]\n\n[Primitive data types]: https://developer.mozilla.org/en-US/docs/Glossary/Primitive\n[`Symbol`]: http://es6.ruanyifeng.com/#docs/symbol\n[`BigInt`]: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/BigInt\n[ES6 中的二进制和八进制表示法]: http://es6.ruanyifeng.com/#docs/number#二进制和八进制表示法\n[ES6 中的模板字符串]: http://es6.ruanyifeng.com/#docs/string#模板字符串\n"
  },
  {
    "path": "basics/type-assertion.md",
    "content": "# 类型断言\n\n类型断言（Type Assertion）可以用来手动指定一个值的类型。\n\n## 语法\n\n```ts\n值 as 类型\n```\n\n或\n\n```ts\n<类型>值\n```\n\n在 tsx 语法（React 的 jsx 语法的 ts 版）中必须使用前者，即 `值 as 类型`。\n\n形如 `<Foo>` 的语法在 tsx 中表示的是一个 `ReactNode`，在 ts 中除了表示类型断言之外，也可能是表示一个[泛型][]。\n\n故建议大家在使用类型断言时，统一使用 `值 as 类型` 这样的语法，本书中也会贯彻这一思想。\n\n## 类型断言的用途\n\n类型断言的常见用途有以下几种：\n\n### 将一个联合类型断言为其中一个类型\n\n[之前提到过](union-types.md#访问联合类型的属性或方法)，当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候，我们**只能访问此联合类型的所有类型中共有的属性或方法**：\n\n```ts\ninterface Cat {\n    name: string;\n    run(): void;\n}\ninterface Fish {\n    name: string;\n    swim(): void;\n}\n\nfunction getName(animal: Cat | Fish) {\n    return animal.name;\n}\n```\n\n而有时候，我们确实需要在还不确定类型的时候就访问其中一个类型特有的属性或方法，比如：\n\n```ts\ninterface Cat {\n    name: string;\n    run(): void;\n}\ninterface Fish {\n    name: string;\n    swim(): void;\n}\n\nfunction isFish(animal: Cat | Fish) {\n    if (typeof animal.swim === 'function') {\n        return true;\n    }\n    return false;\n}\n\n// index.ts:11:23 - error TS2339: Property 'swim' does not exist on type 'Cat | Fish'.\n//   Property 'swim' does not exist on type 'Cat'.\n```\n\n上面的例子中，获取 `animal.swim` 的时候会报错。\n\n此时可以使用类型断言，将 `animal` 断言成 `Fish`：\n\n```ts\ninterface Cat {\n    name: string;\n    run(): void;\n}\ninterface Fish {\n    name: string;\n    swim(): void;\n}\n\nfunction isFish(animal: Cat | Fish) {\n    if (typeof (animal as Fish).swim === 'function') {\n        return true;\n    }\n    return false;\n}\n```\n\n这样就可以解决访问 `animal.swim` 时报错的问题了。\n\n需要注意的是，类型断言只能够「欺骗」TypeScript 编译器，无法避免运行时的错误，反而滥用类型断言可能会导致运行时错误：\n\n```ts\ninterface Cat {\n    name: string;\n    run(): void;\n}\ninterface Fish {\n    name: string;\n    swim(): void;\n}\n\nfunction swim(animal: Cat | Fish) {\n    (animal as Fish).swim();\n}\n\nconst tom: Cat = {\n    name: 'Tom',\n    run() { console.log('run') }\n};\nswim(tom);\n// Uncaught TypeError: animal.swim is not a function`\n```\n\n上面的例子编译时不会报错，但在运行时会报错：\n\n```text\nUncaught TypeError: animal.swim is not a function`\n```\n\n原因是 `(animal as Fish).swim()` 这段代码隐藏了 `animal` 可能为 `Cat` 的情况，将 `animal` 直接断言为 `Fish` 了，而 TypeScript 编译器信任了我们的断言，故在调用 `swim()` 时没有编译错误。\n\n可是 `swim` 函数接受的参数是 `Cat | Fish`，一旦传入的参数是 `Cat` 类型的变量，由于 `Cat` 上没有 `swim` 方法，就会导致运行时错误了。\n\n总之，使用类型断言时一定要格外小心，尽量避免断言后调用方法或引用深层属性，以减少不必要的运行时错误。\n\n### 将一个父类断言为更加具体的子类\n\n当类之间有继承关系时，类型断言也是很常见的：\n\n```ts\nclass ApiError extends Error {\n    code: number = 0;\n}\nclass HttpError extends Error {\n    statusCode: number = 200;\n}\n\nfunction isApiError(error: Error) {\n    if (typeof (error as ApiError).code === 'number') {\n        return true;\n    }\n    return false;\n}\n```\n\n上面的例子中，我们声明了函数 `isApiError`，它用来判断传入的参数是不是 `ApiError` 类型，为了实现这样一个函数，它的参数的类型肯定得是比较抽象的父类 `Error`，这样的话这个函数就能接受 `Error` 或它的子类作为参数了。\n\n但是由于父类 `Error` 中没有 `code` 属性，故直接获取 `error.code` 会报错，需要使用类型断言获取 `(error as ApiError).code`。\n\n大家可能会注意到，在这个例子中有一个更合适的方式来判断是不是 `ApiError`，那就是使用 `instanceof`：\n\n```ts\nclass ApiError extends Error {\n    code: number = 0;\n}\nclass HttpError extends Error {\n    statusCode: number = 200;\n}\n\nfunction isApiError(error: Error) {\n    if (error instanceof ApiError) {\n        return true;\n    }\n    return false;\n}\n```\n\n上面的例子中，确实使用 `instanceof` 更加合适，因为 `ApiError` 是一个 JavaScript 的类，能够通过 `instanceof` 来判断 `error` 是否是它的实例。\n\n但是有的情况下 `ApiError` 和 `HttpError` 不是一个真正的类，而只是一个 TypeScript 的接口（`interface`），接口是一个类型，不是一个真正的值，它在编译结果中会被删除，当然就无法使用 `instanceof` 来做运行时判断了：\n\n```ts\ninterface ApiError extends Error {\n    code: number;\n}\ninterface HttpError extends Error {\n    statusCode: number;\n}\n\nfunction isApiError(error: Error) {\n    if (error instanceof ApiError) {\n        return true;\n    }\n    return false;\n}\n\n// index.ts:9:26 - error TS2693: 'ApiError' only refers to a type, but is being used as a value here.\n```\n\n此时就只能用类型断言，通过判断是否存在 `code` 属性，来判断传入的参数是不是 `ApiError` 了：\n\n```ts\ninterface ApiError extends Error {\n    code: number;\n}\ninterface HttpError extends Error {\n    statusCode: number;\n}\n\nfunction isApiError(error: Error) {\n    if (typeof (error as ApiError).code === 'number') {\n        return true;\n    }\n    return false;\n}\n```\n\n### 将任何一个类型断言为 `any`\n\n理想情况下，TypeScript 的类型系统运转良好，每个值的类型都具体而精确。\n\n当我们引用一个在此类型上不存在的属性或方法时，就会报错：\n\n```ts\nconst foo: number = 1;\nfoo.length = 1;\n\n// index.ts:2:5 - error TS2339: Property 'length' does not exist on type 'number'.\n```\n\n上面的例子中，数字类型的变量 `foo` 上是没有 `length` 属性的，故 TypeScript 给出了相应的错误提示。\n\n这种错误提示显然是非常有用的。\n\n但有的时候，我们非常确定这段代码不会出错，比如下面这个例子：\n\n```ts\nwindow.foo = 1;\n\n// index.ts:1:8 - error TS2339: Property 'foo' does not exist on type 'Window & typeof globalThis'.\n```\n\n上面的例子中，我们需要将 `window` 上添加一个属性 `foo`，但 TypeScript 编译时会报错，提示我们 `window` 上不存在 `foo` 属性。\n\n此时我们可以使用 `as any` 临时将 `window` 断言为 `any` 类型：\n\n```ts\n(window as any).foo = 1;\n```\n\n在 `any` 类型的变量上，访问任何属性都是允许的。\n\n需要注意的是，将一个变量断言为 `any` 可以说是解决 TypeScript 中类型问题的最后一个手段。\n\n**它极有可能掩盖了真正的类型错误，所以如果不是非常确定，就不要使用 `as any`。**\n\n上面的例子中，我们也可以通过[扩展 window 的类型（TODO）][]解决这个错误，不过如果只是临时的增加 `foo` 属性，`as any` 会更加方便。\n\n总之，**一方面不能滥用 `as any`，另一方面也不要完全否定它的作用，我们需要在类型的严格性和开发的便利性之间掌握平衡**（这也是 [TypeScript 的设计理念][]之一），才能发挥出 TypeScript 最大的价值。\n\n### 将 `any` 断言为一个具体的类型\n\n在日常的开发中，我们不可避免的需要处理 `any` 类型的变量，它们可能是由于第三方库未能定义好自己的类型，也有可能是历史遗留的或其他人编写的烂代码，还可能是受到 TypeScript 类型系统的限制而无法精确定义类型的场景。\n\n遇到 `any` 类型的变量时，我们可以选择无视它，任由它滋生更多的 `any`。\n\n我们也可以选择改进它，通过类型断言及时的把 `any` 断言为精确的类型，亡羊补牢，使我们的代码向着高可维护性的目标发展。\n\n举例来说，历史遗留的代码中有个 `getCacheData`，它的返回值是 `any`：\n\n```ts\nfunction getCacheData(key: string): any {\n    return (window as any).cache[key];\n}\n```\n\n那么我们在使用它时，最好能够将调用了它之后的返回值断言成一个精确的类型，这样就方便了后续的操作：\n\n```ts\nfunction getCacheData(key: string): any {\n    return (window as any).cache[key];\n}\n\ninterface Cat {\n    name: string;\n    run(): void;\n}\n\nconst tom = getCacheData('tom') as Cat;\ntom.run();\n```\n\n上面的例子中，我们调用完 `getCacheData` 之后，立即将它断言为 `Cat` 类型。这样的话明确了 `tom` 的类型，后续对 `tom` 的访问时就有了代码补全，提高了代码的可维护性。\n\n## 类型断言的限制\n\n> 本小节的前置知识点：[结构类型系统（TODO）][]、[类型兼容性（TODO）][]\n\n从上面的例子中，我们可以总结出：\n\n- 联合类型可以被断言为其中一个类型\n- 父类可以被断言为子类\n- 任何类型都可以被断言为 any\n- any 可以被断言为任何类型\n\n那么类型断言有没有什么限制呢？是不是任何一个类型都可以被断言为任何另一个类型呢？\n\n答案是否定的——并不是任何一个类型都可以被断言为任何另一个类型。\n\n具体来说，若 `A` 兼容 `B`，那么 `A` 能够被断言为 `B`，`B` 也能被断言为 `A`。\n\n下面我们通过一个简化的例子，来理解类型断言的限制：\n\n```ts\ninterface Animal {\n    name: string;\n}\ninterface Cat {\n    name: string;\n    run(): void;\n}\n\nlet tom: Cat = {\n    name: 'Tom',\n    run: () => { console.log('run') }\n};\nlet animal: Animal = tom;\n```\n\n我们知道，TypeScript 是结构类型系统，类型之间的对比只会比较它们最终的结构，而会忽略它们定义时的关系。\n\n在上面的例子中，`Cat` 包含了 `Animal` 中的所有属性，除此之外，它还有一个额外的方法 `run`。TypeScript 并不关心 `Cat` 和 `Animal` 之间定义时是什么关系，而只会看它们最终的结构有什么关系——所以它与 `Cat extends Animal` 是等价的：\n\n```ts\ninterface Animal {\n    name: string;\n}\ninterface Cat extends Animal {\n    run(): void;\n}\n```\n\n那么也不难理解为什么 `Cat` 类型的 `tom` 可以赋值给 `Animal` 类型的 `animal` 了——就像面向对象编程中我们可以将子类的实例赋值给类型为父类的变量。\n\n我们把它换成 TypeScript 中更专业的说法，即：`Animal` 兼容 `Cat`。\n\n当 `Animal` 兼容 `Cat` 时，它们就可以互相进行类型断言了：\n\n```ts\ninterface Animal {\n    name: string;\n}\ninterface Cat {\n    name: string;\n    run(): void;\n}\n\nfunction testAnimal(animal: Animal) {\n    return (animal as Cat);\n}\nfunction testCat(cat: Cat) {\n    return (cat as Animal);\n}\n```\n\n这样的设计其实也很容易就能理解：\n\n- 允许 `animal as Cat` 是因为「父类可以被断言为子类」，这个前面已经学习过了\n- 允许 `cat as Animal` 是因为既然子类拥有父类的属性和方法，那么被断言为父类，获取父类的属性、调用父类的方法，就不会有任何问题，故「子类可以被断言为父类」\n\n需要注意的是，这里我们使用了简化的父类子类的关系来表达类型的兼容性，而实际上 TypeScript 在判断类型的兼容性时，比这种情况复杂很多，详细请参考[类型的兼容性（TODO)][]章节。\n\n总之，若 `A` 兼容 `B`，那么 `A` 能够被断言为 `B`，`B` 也能被断言为 `A`。\n\n同理，若 `B` 兼容 `A`，那么 `A` 能够被断言为 `B`，`B` 也能被断言为 `A`。\n\n所以这也可以换一种说法：\n\n要使得 `A` 能够被断言为 `B`，只需要 `A` 兼容 `B` 或 `B` 兼容 `A` 即可，这也是为了在类型断言时的安全考虑，毕竟毫无根据的断言是非常危险的。\n\n综上所述：\n\n- 联合类型可以被断言为其中一个类型\n- 父类可以被断言为子类\n- 任何类型都可以被断言为 any\n- any 可以被断言为任何类型\n- 要使得 `A` 能够被断言为 `B`，只需要 `A` 兼容 `B` 或 `B` 兼容 `A` 即可\n\n其实前四种情况都是最后一个的特例。\n\n## 双重断言\n\n既然：\n\n- 任何类型都可以被断言为 any\n- any 可以被断言为任何类型\n\n那么我们是不是可以使用双重断言 `as any as Foo` 来将任何一个类型断言为任何另一个类型呢？\n\n```ts\ninterface Cat {\n    run(): void;\n}\ninterface Fish {\n    swim(): void;\n}\n\nfunction testCat(cat: Cat) {\n    return (cat as any as Fish);\n}\n```\n\n在上面的例子中，若直接使用 `cat as Fish` 肯定会报错，因为 `Cat` 和 `Fish` 互相都不兼容。\n\n但是若使用双重断言，则可以打破「要使得 `A` 能够被断言为 `B`，只需要 `A` 兼容 `B` 或 `B` 兼容 `A` 即可」的限制，将任何一个类型断言为任何另一个类型。\n\n若你使用了这种双重断言，那么十有八九是非常错误的，它很可能会导致运行时错误。\n\n**除非迫不得已，千万别用双重断言。**\n\n## 类型断言 vs 类型转换\n\n类型断言只会影响 TypeScript 编译时的类型，类型断言语句在编译结果中会被删除：\n\n```ts\nfunction toBoolean(something: any): boolean {\n    return something as boolean;\n}\n\ntoBoolean(1);\n// 返回值为 1\n```\n\n在上面的例子中，将 `something` 断言为 `boolean` 虽然可以通过编译，但是并没有什么用，代码在编译后会变成：\n\n```js\nfunction toBoolean(something) {\n    return something;\n}\n\ntoBoolean(1);\n// 返回值为 1\n```\n\n所以类型断言不是类型转换，它不会真的影响到变量的类型。\n\n若要进行类型转换，需要直接调用类型转换的方法：\n\n```ts\nfunction toBoolean(something: any): boolean {\n    return Boolean(something);\n}\n\ntoBoolean(1);\n// 返回值为 true\n```\n\n## 类型断言 vs 类型声明\n\n在这个例子中：\n\n```ts\nfunction getCacheData(key: string): any {\n    return (window as any).cache[key];\n}\n\ninterface Cat {\n    name: string;\n    run(): void;\n}\n\nconst tom = getCacheData('tom') as Cat;\ntom.run();\n```\n\n我们使用 `as Cat` 将 `any` 类型断言为了 `Cat` 类型。\n\n但实际上还有其他方式可以解决这个问题：\n\n```ts\nfunction getCacheData(key: string): any {\n    return (window as any).cache[key];\n}\n\ninterface Cat {\n    name: string;\n    run(): void;\n}\n\nconst tom: Cat = getCacheData('tom');\ntom.run();\n```\n\n上面的例子中，我们通过类型声明的方式，将 `tom` 声明为 `Cat`，然后再将 `any` 类型的 `getCacheData('tom')` 赋值给 `Cat` 类型的 `tom`。\n\n这和类型断言是非常相似的，而且产生的结果也几乎是一样的——`tom` 在接下来的代码中都变成了 `Cat` 类型。\n\n它们的区别，可以通过这个例子来理解：\n\n```ts\ninterface Animal {\n    name: string;\n}\ninterface Cat {\n    name: string;\n    run(): void;\n}\n\nconst animal: Animal = {\n    name: 'tom'\n};\nlet tom = animal as Cat;\n```\n\n在上面的例子中，由于 `Animal` 兼容 `Cat`，故可以将 `animal` 断言为 `Cat` 赋值给 `tom`。\n\n但是若直接声明 `tom` 为 `Cat` 类型：\n\n```ts\ninterface Animal {\n    name: string;\n}\ninterface Cat {\n    name: string;\n    run(): void;\n}\n\nconst animal: Animal = {\n    name: 'tom'\n};\nlet tom: Cat = animal;\n\n// index.ts:12:5 - error TS2741: Property 'run' is missing in type 'Animal' but required in type 'Cat'.\n```\n\n则会报错，不允许将 `animal` 赋值为 `Cat` 类型的 `tom`。\n\n这很容易理解，`Animal` 可以看作是 `Cat` 的父类，当然不能将父类的实例赋值给类型为子类的变量。\n\n深入的讲，它们的核心区别就在于：\n\n- `animal` 断言为 `Cat`，只需要满足 `Animal` 兼容 `Cat` 或 `Cat` 兼容 `Animal` 即可\n- `animal` 赋值给 `tom`，需要满足 `Cat` 兼容 `Animal` 才行\n\n但是 `Cat` 并不兼容 `Animal`。\n\n而在前一个例子中，由于 `getCacheData('tom')` 是 `any` 类型，`any` 兼容 `Cat`，`Cat` 也兼容 `any`，故\n\n```ts\nconst tom = getCacheData('tom') as Cat;\n```\n\n等价于\n\n```ts\nconst tom: Cat = getCacheData('tom');\n```\n\n知道了它们的核心区别，就知道了类型声明是比类型断言更加严格的。\n\n所以为了增加代码的质量，我们最好优先使用类型声明，这也比类型断言的 `as` 语法更加优雅。\n\n## 类型断言 vs 泛型\n\n> 本小节的前置知识点：[泛型][]\n\n还是这个例子：\n\n```ts\nfunction getCacheData(key: string): any {\n    return (window as any).cache[key];\n}\n\ninterface Cat {\n    name: string;\n    run(): void;\n}\n\nconst tom = getCacheData('tom') as Cat;\ntom.run();\n```\n\n我们还有第三种方式可以解决这个问题，那就是泛型：\n\n```ts\nfunction getCacheData<T>(key: string): T {\n    return (window as any).cache[key];\n}\n\ninterface Cat {\n    name: string;\n    run(): void;\n}\n\nconst tom = getCacheData<Cat>('tom');\ntom.run();\n```\n\n通过给 `getCacheData` 函数添加了一个泛型 `<T>`，我们可以更加规范的实现对 `getCacheData` 返回值的约束，这也同时去除掉了代码中的 `any`，是最优的一个解决方案。\n\n## 参考\n\n- [TypeScript Deep Dive / Type Assertion](https://basarat.gitbooks.io/typescript/content/docs/types/type-assertion.html)\n- [Advanced Types # Type Guards and Differentiating Types](http://www.typescriptlang.org/docs/handbook/advanced-types.html#type-guards-and-differentiating-types)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Advanced%20Types.html#类型保护与区分类型（type-guards-and-differentiating-types）)）\n- [TypeScript 的设计理念][]\n\n[TypeScript 的设计理念]: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals\n[泛型]: ../advanced/generics.md\n"
  },
  {
    "path": "basics/type-inference.md",
    "content": "# 类型推论\n\n如果没有明确的指定类型，那么 TypeScript 会依照类型推论（Type Inference）的规则推断出一个类型。\n\n## 什么是类型推论\n\n以下代码虽然没有指定类型，但是会在编译的时候报错：\n\n```ts\nlet myFavoriteNumber = 'seven';\nmyFavoriteNumber = 7;\n\n// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.\n```\n\n事实上，它等价于：\n\n```ts\nlet myFavoriteNumber: string = 'seven';\nmyFavoriteNumber = 7;\n\n// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.\n```\n\nTypeScript 会在没有明确的指定类型的时候推测出一个类型，这就是类型推论。\n\n**如果定义的时候没有赋值，不管之后有没有赋值，都会被推断成 `any` 类型而完全不被类型检查**：\n\n```ts\nlet myFavoriteNumber;\nmyFavoriteNumber = 'seven';\nmyFavoriteNumber = 7;\n```\n\n## 参考\n\n- [Type Inference](http://www.typescriptlang.org/docs/handbook/type-inference.html)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Type%20Inference.html)）\n"
  },
  {
    "path": "basics/type-of-array.md",
    "content": "# 数组的类型\n\n在 TypeScript 中，数组类型有多种定义方式，比较灵活。\n\n## 「类型 + 方括号」表示法\n\n最简单的方法是使用「类型 + 方括号」来表示数组：\n\n```ts\nlet fibonacci: number[] = [1, 1, 2, 3, 5];\n```\n\n数组的项中**不允许**出现其他的类型：\n\n```ts\nlet fibonacci: number[] = [1, '1', 2, 3, 5];\n\n// Type 'string' is not assignable to type 'number'.\n```\n\n数组的一些方法的参数也会根据数组在定义时约定的类型进行限制：\n\n```ts\nlet fibonacci: number[] = [1, 1, 2, 3, 5];\nfibonacci.push('8');\n\n// Argument of type '\"8\"' is not assignable to parameter of type 'number'.\n```\n\n上例中，`push` 方法只允许传入 `number` 类型的参数，但是却传了一个 `\"8\"` 类型的参数，所以报错了。这里 `\"8\"` 是一个字符串字面量类型，会在后续章节中详细介绍。\n\n## 数组泛型\n\n我们也可以使用数组泛型（Array Generic） `Array<elemType>` 来表示数组：\n\n```ts\nlet fibonacci: Array<number> = [1, 1, 2, 3, 5];\n```\n\n关于泛型，可以参考[泛型](../advanced/generics.md)一章。\n\n## 用接口表示数组\n\n接口也可以用来描述数组：\n\n```ts\ninterface NumberArray {\n    [index: number]: number;\n}\nlet fibonacci: NumberArray = [1, 1, 2, 3, 5];\n```\n\n`NumberArray` 表示：只要索引的类型是数字时，那么值的类型必须是数字。\n\n虽然接口也可以用来描述数组，但是我们一般不会这么做，因为这种方式比前两种方式复杂多了。\n\n不过有一种情况例外，那就是它常用来表示类数组。\n\n## 类数组\n\n类数组（Array-like Object）不是数组类型，比如 `arguments`：\n\n```ts\nfunction sum() {\n    let args: number[] = arguments;\n}\n\n// Type 'IArguments' is missing the following properties from type 'number[]': pop, push, concat, join, and 24 more.\n```\n\n上例中，`arguments` 实际上是一个类数组，不能用普通的数组的方式来描述，而应该用接口：\n\n```ts\nfunction sum() {\n    let args: {\n        [index: number]: number;\n        length: number;\n        callee: Function;\n    } = arguments;\n}\n```\n\n在这个例子中，我们除了约束当索引的类型是数字时，值的类型必须是数字之外，也约束了它还有 `length` 和 `callee` 两个属性。\n\n事实上常用的类数组都有自己的接口定义，如 `IArguments`, `NodeList`, `HTMLCollection` 等：\n\n```ts\nfunction sum() {\n    let args: IArguments = arguments;\n}\n```\n\n其中 `IArguments` 是 TypeScript 中定义好了的类型，它实际上就是：\n\n```ts\ninterface IArguments {\n    [index: number]: any;\n    length: number;\n    callee: Function;\n}\n```\n\n关于内置对象，可以参考[内置对象](./built-in-objects.md)一章。\n\n## any 在数组中的应用\n\n一个比较常见的做法是，用 `any` 表示数组中允许出现任意类型：\n\n```ts\nlet list: any[] = ['xcatliu', 25, { website: 'http://xcatliu.com' }];\n```\n\n## 参考\n\n- [Basic Types # Array](http://www.typescriptlang.org/docs/handbook/basic-types.html#array)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Basic%20Types.html#数组)）\n- [Interfaces # Indexable Types](http://www.typescriptlang.org/docs/handbook/interfaces.html#indexable-types)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Interfaces.html#数组类型)）\n"
  },
  {
    "path": "basics/type-of-function.md",
    "content": "# 函数的类型\n\n> [函数是 JavaScript 中的一等公民](https://llh911001.gitbooks.io/mostly-adequate-guide-chinese/content/ch2.html)\n\n## 函数声明\n\n在 JavaScript 中，有两种常见的定义函数的方式——函数声明（Function Declaration）和函数表达式（Function Expression）：\n\n```js\n// 函数声明（Function Declaration）\nfunction sum(x, y) {\n    return x + y;\n}\n\n// 函数表达式（Function Expression）\nlet mySum = function (x, y) {\n    return x + y;\n};\n```\n\n一个函数有输入和输出，要在 TypeScript 中对其进行约束，需要把输入和输出都考虑到，其中函数声明的类型定义较简单：\n\n```ts\nfunction sum(x: number, y: number): number {\n    return x + y;\n}\n```\n\n注意，**输入多余的（或者少于要求的）参数，是不被允许的**：\n\n```ts\nfunction sum(x: number, y: number): number {\n    return x + y;\n}\nsum(1, 2, 3);\n\n// index.ts(4,1): error TS2346: Supplied parameters do not match any signature of call target.\n```\n\n```ts\nfunction sum(x: number, y: number): number {\n    return x + y;\n}\nsum(1);\n\n// index.ts(4,1): error TS2346: Supplied parameters do not match any signature of call target.\n```\n\n## 函数表达式\n\n如果要我们现在写一个函数表达式（Function Expression）的定义，可能会写成这样：\n\n```ts\nlet mySum = function (x: number, y: number): number {\n    return x + y;\n};\n```\n\n这是可以通过编译的，不过事实上，上面的代码只对等号右侧的匿名函数进行了类型定义，而等号左边的 `mySum`，是通过赋值操作进行类型推论而推断出来的。如果需要我们手动给 `mySum` 添加类型，则应该是这样：\n\n```ts\nlet mySum: (x: number, y: number) => number = function (x: number, y: number): number {\n    return x + y;\n};\n```\n\n注意不要混淆了 TypeScript 中的 `=>` 和 ES6 中的 `=>`。\n\n在 TypeScript 的类型定义中，`=>` 用来表示函数的定义，左边是输入类型，需要用括号括起来，右边是输出类型。\n\n在 ES6 中，`=>` 叫做箭头函数，应用十分广泛，可以参考 [ES6 中的箭头函数][]。\n\n## 用接口定义函数的形状\n\n我们也可以使用接口的方式来定义一个函数需要符合的形状：\n\n```ts\ninterface SearchFunc {\n    (source: string, subString: string): boolean;\n}\n\nlet mySearch: SearchFunc;\nmySearch = function(source: string, subString: string) {\n    return source.search(subString) !== -1;\n}\n```\n\n采用函数表达式|接口定义函数的方式时，对等号左侧进行类型限制，可以保证以后对函数名赋值时保证参数个数、参数类型、返回值类型不变。\n\n## 可选参数\n\n前面提到，输入多余的（或者少于要求的）参数，是不允许的。那么如何定义可选的参数呢？\n\n与接口中的可选属性类似，我们用 `?` 表示可选的参数：\n\n```ts\nfunction buildName(firstName: string, lastName?: string) {\n    if (lastName) {\n        return firstName + ' ' + lastName;\n    } else {\n        return firstName;\n    }\n}\nlet tomcat = buildName('Tom', 'Cat');\nlet tom = buildName('Tom');\n```\n\n需要注意的是，可选参数必须接在必需参数后面。换句话说，**可选参数后面不允许再出现必需参数了**：\n\n```ts\nfunction buildName(firstName?: string, lastName: string) {\n    if (firstName) {\n        return firstName + ' ' + lastName;\n    } else {\n        return lastName;\n    }\n}\nlet tomcat = buildName('Tom', 'Cat');\nlet tom = buildName(undefined, 'Tom');\n\n// index.ts(1,40): error TS1016: A required parameter cannot follow an optional parameter.\n```\n\n## 参数默认值\n\n在 ES6 中，我们允许给函数的参数添加默认值，**TypeScript 会将添加了默认值的参数识别为可选参数**：\n\n```ts\nfunction buildName(firstName: string, lastName: string = 'Cat') {\n    return firstName + ' ' + lastName;\n}\nlet tomcat = buildName('Tom', 'Cat');\nlet tom = buildName('Tom');\n```\n\n此时就不受「可选参数必须接在必需参数后面」的限制了：\n\n```ts\nfunction buildName(firstName: string = 'Tom', lastName: string) {\n    return firstName + ' ' + lastName;\n}\nlet tomcat = buildName('Tom', 'Cat');\nlet cat = buildName(undefined, 'Cat');\n```\n\n> 关于默认参数，可以参考 [ES6 中函数参数的默认值][]。\n\n## 剩余参数\n\nES6 中，可以使用 `...rest` 的方式获取函数中的剩余参数（rest 参数）：\n\n```js\nfunction push(array, ...items) {\n    items.forEach(function(item) {\n        array.push(item);\n    });\n}\n\nlet a: any[] = [];\npush(a, 1, 2, 3);\n```\n\n事实上，`items` 是一个数组。所以我们可以用数组的类型来定义它：\n\n```ts\nfunction push(array: any[], ...items: any[]) {\n    items.forEach(function(item) {\n        array.push(item);\n    });\n}\n\nlet a = [];\npush(a, 1, 2, 3);\n```\n\n注意，rest 参数只能是最后一个参数，关于 rest 参数，可以参考 [ES6 中的 rest 参数][]。\n\n## 重载\n\n重载允许一个函数接受不同数量或类型的参数时，作出不同的处理。\n\n比如，我们需要实现一个函数 `reverse`，输入数字 `123` 的时候，输出反转的数字 `321`，输入字符串 `'hello'` 的时候，输出反转的字符串 `'olleh'`。\n\n利用联合类型，我们可以这么实现：\n\n```ts\nfunction reverse(x: number | string): number | string | void {\n    if (typeof x === 'number') {\n        return Number(x.toString().split('').reverse().join(''));\n    } else if (typeof x === 'string') {\n        return x.split('').reverse().join('');\n    }\n}\n```\n\n**然而这样有一个缺点，就是不能够精确的表达，输入为数字的时候，输出也应该为数字，输入为字符串的时候，输出也应该为字符串。**\n\n这时，我们可以使用重载定义多个 `reverse` 的函数类型：\n\n```ts\nfunction reverse(x: number): number;\nfunction reverse(x: string): string;\nfunction reverse(x: number | string): number | string | void {\n    if (typeof x === 'number') {\n        return Number(x.toString().split('').reverse().join(''));\n    } else if (typeof x === 'string') {\n        return x.split('').reverse().join('');\n    }\n}\n```\n\n上例中，我们重复定义了多次函数 `reverse`，前几次都是函数定义，最后一次是函数实现。在编辑器的代码提示中，可以正确的看到前两个提示。\n\n注意，TypeScript 会优先从最前面的函数定义开始匹配，所以多个函数定义如果有包含关系，需要优先把精确的定义写在前面。\n\n## 参考\n\n- [Functions](http://www.typescriptlang.org/docs/handbook/functions.html)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Functions.html)）\n- [Functions # Function Types](http://www.typescriptlang.org/docs/handbook/interfaces.html#function-types)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Interfaces.html#函数类型)）\n- [JS 函数式编程指南](https://llh911001.gitbooks.io/mostly-adequate-guide-chinese/content/)\n- [ES6 中的箭头函数]\n- [ES6 中函数参数的默认值]\n- [ES6 中的 rest 参数]\n\n[ES6 中的箭头函数]: http://es6.ruanyifeng.com/#docs/function#箭头函数\n[ES6 中函数参数的默认值]: http://es6.ruanyifeng.com/#docs/function#函数参数的默认值\n[ES6 中的 rest 参数]: http://es6.ruanyifeng.com/#docs/function#rest参数\n"
  },
  {
    "path": "basics/type-of-object-interfaces.md",
    "content": "# 对象的类型——接口\n\n在 TypeScript 中，我们使用接口（Interfaces）来定义对象的类型。\n\n## 什么是接口\n\n在面向对象语言中，接口（Interfaces）是一个很重要的概念，它是对行为的抽象，而具体如何行动需要由类（classes）去实现（implement）。\n\nTypeScript 中的接口是一个非常灵活的概念，除了可用于[对类的一部分行为进行抽象](../advanced/class-and-interfaces.md#类实现接口)以外，也常用于对「对象的形状（Shape）」进行描述。\n\n## 简单的例子\n\n```ts\ninterface Person {\n    name: string;\n    age: number;\n}\n\nlet tom: Person = {\n    name: 'Tom',\n    age: 25\n};\n```\n\n上面的例子中，我们定义了一个接口 `Person`，接着定义了一个变量 `tom`，它的类型是 `Person`。这样，我们就约束了 `tom` 的形状必须和接口 `Person` 一致。\n\n接口一般首字母大写。[有的编程语言中会建议接口的名称加上 `I` 前缀](https://msdn.microsoft.com/en-us/library/8bc1fexb%28v=vs.71%29.aspx)。\n\n定义的变量比接口少了一些属性是不允许的：\n\n```ts\ninterface Person {\n    name: string;\n    age: number;\n}\n\nlet tom: Person = {\n    name: 'Tom'\n};\n\n// index.ts(6,5): error TS2322: Type '{ name: string; }' is not assignable to type 'Person'.\n//   Property 'age' is missing in type '{ name: string; }'.\n```\n\n多一些属性也是不允许的：\n\n```ts\ninterface Person {\n    name: string;\n    age: number;\n}\n\nlet tom: Person = {\n    name: 'Tom',\n    age: 25,\n    gender: 'male'\n};\n\n// index.ts(9,5): error TS2322: Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'.\n//   Object literal may only specify known properties, and 'gender' does not exist in type 'Person'.\n```\n\n可见，**赋值的时候，变量的形状必须和接口的形状保持一致**。\n\n## 可选属性\n\n有时我们希望不要完全匹配一个形状，那么可以用可选属性：\n\n```ts\ninterface Person {\n    name: string;\n    age?: number;\n}\n\nlet tom: Person = {\n    name: 'Tom'\n};\n```\n\n```ts\ninterface Person {\n    name: string;\n    age?: number;\n}\n\nlet tom: Person = {\n    name: 'Tom',\n    age: 25\n};\n```\n\n可选属性的含义是该属性可以不存在。\n\n这时**仍然不允许添加未定义的属性**：\n\n```ts\ninterface Person {\n    name: string;\n    age?: number;\n}\n\nlet tom: Person = {\n    name: 'Tom',\n    age: 25,\n    gender: 'male'\n};\n\n// examples/playground/index.ts(9,5): error TS2322: Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'.\n//   Object literal may only specify known properties, and 'gender' does not exist in type 'Person'.\n```\n\n## 任意属性\n\n有时候我们希望一个接口允许有任意的属性，可以使用如下方式：\n\n```ts\ninterface Person {\n    name: string;\n    age?: number;\n    [propName: string]: any;\n}\n\nlet tom: Person = {\n    name: 'Tom',\n    gender: 'male'\n};\n```\n\n使用 `[propName: string]` 定义了任意属性取 `string` 类型的值。\n\n需要注意的是，**一旦定义了任意属性，那么确定属性和可选属性的类型都必须是它的类型的子集**：\n\n```ts\ninterface Person {\n    name: string;\n    age?: number;\n    [propName: string]: string;\n}\n\nlet tom: Person = {\n    name: 'Tom',\n    age: 25,\n    gender: 'male'\n};\n\n// index.ts(3,5): error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'.\n// index.ts(7,5): error TS2322: Type '{ [x: string]: string | number; name: string; age: number; gender: string; }' is not assignable to type 'Person'.\n//   Index signatures are incompatible.\n//     Type 'string | number' is not assignable to type 'string'.\n//       Type 'number' is not assignable to type 'string'.\n```\n\n上例中，任意属性的值允许是 `string`，但是可选属性 `age` 的值却是 `number`，`number` 不是 `string` 的子属性，所以报错了。\n\n另外，在报错信息中可以看出，此时 `{ name: 'Tom', age: 25, gender: 'male' }` 的类型被推断成了 `{ [x: string]: string | number; name: string; age: number; gender: string; }`，这是联合类型和接口的结合。\n\n一个接口中只能定义一个任意属性。如果接口中有多个类型的属性，则可以在任意属性中使用联合类型：\n```ts\ninterface Person {\n    name: string;\n    age?: number;\n    [propName: string]: string | number;\n}\n\nlet tom: Person = {\n    name: 'Tom',\n    age: 25,\n    gender: 'male'\n};\n```\n\n## 只读属性\n\n有时候我们希望对象中的一些字段只能在创建的时候被赋值，那么可以用 `readonly` 定义只读属性：\n\n```ts\ninterface Person {\n    readonly id: number;\n    name: string;\n    age?: number;\n    [propName: string]: any;\n}\n\nlet tom: Person = {\n    id: 89757,\n    name: 'Tom',\n    gender: 'male'\n};\n\ntom.id = 9527;\n\n// index.ts(14,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.\n```\n\n上例中，使用 `readonly` 定义的属性 `id` 初始化后，又被赋值了，所以报错了。\n\n**注意，只读的约束存在于第一次给对象赋值的时候，而不是第一次给只读属性赋值的时候**：\n\n```ts\ninterface Person {\n    readonly id: number;\n    name: string;\n    age?: number;\n    [propName: string]: any;\n}\n\nlet tom: Person = {\n    name: 'Tom',\n    gender: 'male'\n};\n\ntom.id = 89757;\n\n// index.ts(8,5): error TS2322: Type '{ name: string; gender: string; }' is not assignable to type 'Person'.\n//   Property 'id' is missing in type '{ name: string; gender: string; }'.\n// index.ts(13,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.\n```\n\n上例中，报错信息有两处，第一处是在对 `tom` 进行赋值的时候，没有给 `id` 赋值。\n\n第二处是在给 `tom.id` 赋值的时候，由于它是只读属性，所以报错了。\n\n## 参考 \n\n- [Interfaces](http://www.typescriptlang.org/docs/handbook/interfaces.html)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Interfaces.html)）\n"
  },
  {
    "path": "basics/union-types.md",
    "content": "# 联合类型\n\n联合类型（Union Types）表示取值可以为多种类型中的一种。\n\n## 简单的例子\n\n```ts\nlet myFavoriteNumber: string | number;\nmyFavoriteNumber = 'seven';\nmyFavoriteNumber = 7;\n```\n\n```ts\nlet myFavoriteNumber: string | number;\nmyFavoriteNumber = true;\n\n// index.ts(2,1): error TS2322: Type 'boolean' is not assignable to type 'string | number'.\n//   Type 'boolean' is not assignable to type 'number'.\n```\n\n联合类型使用 `|` 分隔每个类型。\n\n这里的 `let myFavoriteNumber: string | number` 的含义是，允许 `myFavoriteNumber` 的类型是 `string` 或者 `number`，但是不能是其他类型。\n\n## 访问联合类型的属性或方法\n\n当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候，我们**只能访问此联合类型的所有类型里共有的属性或方法**：\n\n```ts\nfunction getLength(something: string | number): number {\n    return something.length;\n}\n\n// index.ts(2,22): error TS2339: Property 'length' does not exist on type 'string | number'.\n//   Property 'length' does not exist on type 'number'.\n```\n\n上例中，`length` 不是 `string` 和 `number` 的共有属性，所以会报错。\n\n访问 `string` 和 `number` 的共有属性是没问题的：\n\n```ts\nfunction getString(something: string | number): string {\n    return something.toString();\n}\n```\n\n联合类型的变量在被赋值的时候，会根据类型推论的规则推断出一个类型：\n\n```ts\nlet myFavoriteNumber: string | number;\nmyFavoriteNumber = 'seven';\nconsole.log(myFavoriteNumber.length); // 5\nmyFavoriteNumber = 7;\nconsole.log(myFavoriteNumber.length); // 编译时报错\n\n// index.ts(5,30): error TS2339: Property 'length' does not exist on type 'number'.\n```\n\n上例中，第二行的 `myFavoriteNumber` 被推断成了 `string`，访问它的 `length` 属性不会报错。\n\n而第四行的 `myFavoriteNumber` 被推断成了 `number`，访问它的 `length` 属性时就报错了。\n\n## 参考\n\n- [Advanced Types # Union Types](http://www.typescriptlang.org/docs/handbook/advanced-types.html#union-types)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Advanced%20Types.html#联合类型)）\n"
  },
  {
    "path": "engineering/README.md",
    "content": "# 工程\n\n掌握了 TypeScript 的语法就像学会了砌墙的工艺。\n\n我们学习 TypeScript 的目的不是为了造一间小茅屋，而是为了造高楼大厦，这也正是 TypeScript 的类型系统带来的优势。\n\n那么一项大工程应该如何开展呢？本部分的内容就会介绍 TypeScript 工程化的最佳实践，具体内容包括：\n\n- [代码检查](lint.md)\n- [编译选项](compiler-options.md)\n"
  },
  {
    "path": "engineering/compiler-options.md",
    "content": "# 编译选项\n\nTypeScript 提供了非常多的编译选项，但是官方文档对每一项的解释很抽象，这一章会详细介绍每一个选项的作用，并给出对应的示例。\n\n索引（点击选项跳转到详细介绍）：\n\n选项 | 类型 | 默认值 | 描述\n--- | --- | --- | ---\n[`allowJs`](#allowjs) | `boolean` | `false` | 允许编译 js 文件\n[`allowSyntheticDefaultImports`](#allowsyntheticdefaultimports) | `boolean` | `false` | 允许对不包含默认导出的模块使用默认导入。这个选项不会影响生成的代码，只会影响类型检查。\n\n## allowJs\n\n> 允许编译 js 文件。\n\n设置为 `true` 时，js 文件会被 tsc 编译，否则不会。一般在项目中 js, ts 混合开发时需要设置。\n\n[查看示例](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/compiler-options/01-allowJs)\n\n```bash\n# 设置为 true 时，编译后的文件包含 foo.js\n├── lib\n│   ├── foo.js\n│   └── index.js\n├── src\n│   ├── foo.js\n│   └── index.ts\n├── package.json\n└── tsconfig.json\n```\n\n```bash\n# 设置为 false 时，编译后的文件不包含 foo.js\n├── lib\n│   └── index.js\n├── src\n│   ├── foo.js\n│   └── index.ts\n├── package.json\n└── tsconfig.json\n```\n\n## allowSyntheticDefaultImports\n\n> 允许对不包含默认导出的模块使用默认导入。这个选项不会影响生成的代码，只会影响类型检查。\n\n`export = foo` 是 ts 为了兼容 commonjs 创造的语法，它对应于 commonjs 中的 `module.exports = foo`。\n\n在 ts 中，如果要引入一个通过 `export = foo` 导出的模块，标准的语法是 `import foo = require('foo')`，或者 `import * as foo from 'foo'`。\n\n但由于历史原因，我们已经习惯了使用 `import foo from 'foo'`。\n\n这个选项就是为了解决这个问题。当它设置为 `true` 时，允许使用 `import foo from 'foo'` 来导入一个通过 `export = foo` 导出的模块。当它设置为 `false` 时，则不允许，会报错。\n\n当然，我们一般不会在 ts 文件中使用 `export = foo` 来导出模块，而是在[写（符合 commonjs 规范的）第三方库的声明文件](../basics/declaration-files#export-1)时，才会用到 `export = foo` 来导出类型。\n\n比如 React 的声明文件中，就是通过 `export = React` 来导出类型：\n\n```ts\nexport = React;\nexport as namespace React;\n\ndeclare namespace React {\n    // 声明 React 的类型\n}\n```\n\n此时若我们通过 `import React from 'react'` 来导入 react 则会报错，[查看示例](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/compiler-options/02-allowSyntheticDefaultImports)\n：\n\n```ts\nimport React from 'react';\n// Module '\"typescript-tutorial/examples/compiler-options/02-allowSyntheticDefaultImports/false/node_modules/@types/react/index\"' can only be default-imported using the 'esModuleInterop' flagts(1259)\n```\n\n解决办法就是将 `allowSyntheticDefaultImports` 设置为 `true`。\n"
  },
  {
    "path": "engineering/lint.md",
    "content": "# 代码检查\n\n2019 年 1 月，[TypeScirpt 官方决定全面采用 ESLint](https://www.oschina.net/news/103818/future-typescript-eslint) 作为代码检查的工具，并创建了一个新项目 [typescript-eslint][]，提供了 TypeScript 文件的解析器 [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/parser) 和相关的配置选项 [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin) 等。而之前的两个 lint 解决方案都将弃用：\n\n- [typescript-eslint-parser](https://github.com/eslint/typescript-eslint-parser) 已停止维护\n- [TSLint](https://palantir.github.io/tslint/) 将提供迁移工具，并在 typescript-eslint 的功能足够完整后停止维护 TSLint（Once we consider ESLint feature-complete w.r.t. TSLint, we will deprecate TSLint and help users migrate to ESLint<sup>[1](https://medium.com/palantir/tslint-in-2019-1a144c2317a9)</sup>）\n\n综上所述，目前以及将来的 TypeScript 的代码检查方案就是 [typescript-eslint][]。\n\n## 什么是代码检查\n\n代码检查主要是用来发现代码错误、统一代码风格。\n\n在 JavaScript 项目中，我们一般使用 [ESLint][] 来进行代码检查，它通过插件化的特性极大的丰富了适用范围，搭配 [typescript-eslint][] 之后，甚至可以用来检查 TypeScript 代码。\n\n## 为什么需要代码检查\n\n有人会觉得，JavaScript 非常灵活，所以需要代码检查。而 TypeScript 已经能够在编译阶段检查出很多问题了，为什么还需要代码检查呢？\n\n因为 TypeScript 关注的重心是类型的检查，而不是代码风格。当团队的人员越来越多时，同样的逻辑不同的人写出来可能会有很大的区别：\n\n- 缩进应该是四个空格还是两个空格？\n- 是否应该禁用 `var`？\n- 接口名是否应该以 `I` 开头？\n- 是否应该强制使用 `===` 而不是 `==`？\n\n这些问题 TypeScript 不会关注，但是却影响到多人协作开发时的效率、代码的可理解性以及可维护性。\n\n下面来看一个具体的例子：\n\n```ts\nvar myName = 'Tom';\n\nconsole.log(`My name is ${myNane}`);\nconsole.log(`My name is ${myName.toStrng()}`);\n```\n\n以上代码你能看出有什么错误吗？\n\n分别用 tsc 编译和 eslint 检查后，报错信息如下：\n\n```ts\nvar myName = 'Tom';\n// eslint 报错信息：\n// Unexpected var, use let or const instead.eslint(no-var)\n\nconsole.log(`My name is ${myNane}`);\n// tsc 报错信息：\n// Cannot find name 'myNane'. Did you mean 'myName'?\n// eslint 报错信息：\n// 'myNane' is not defined.eslint(no-undef)\nconsole.log(`My name is ${myName.toStrng()}`);\n// tsc 报错信息：\n// Property 'toStrng' does not exist on type 'string'. Did you mean 'toString'?\n```\n\n| 存在的问题 | `tsc` 是否报错 | `eslint` 是否报错 |\n| --- | --- | --- |\n| 应该使用 `let` 或 `const` 而不是 `var` | ❌ | ✅ |\n| `myName` 被误写成了 `myNane` | ✅ | ✅ |\n| `toString` 被误写成了 `toStrng` | ✅️ | ❌ |\n\n上例中，我们使用了 `var` 来定义一个变量，但其实 ES6 中有更先进的语法 `let` 和 `const`，此时就可以通过 `eslint` 检查出来，提示我们应该使用 `let` 或 `const` 而不是 `var`。\n\n对于未定义的变量 `myNane`，`tsc` 和 `eslint` 都可以检查出来。\n\n由于 `eslint` 无法识别 `myName` 存在哪些方法，所以对于拼写错误的 `toString` 没有检查出来。\n\n由此可见，`eslint` 能够发现出一些 `tsc` 不会关心的错误，检查出一些潜在的问题，所以代码检查还是非常重要的。\n\n## 在 TypeScript 中使用 ESLint\n\n### 安装 ESLint\n\nESLint 可以安装在当前项目中或全局环境下，因为代码检查是项目的重要组成部分，所以我们一般会将它安装在当前项目中。可以运行下面的脚本来安装：\n\n```bash\nnpm install --save-dev eslint\n```\n\n由于 ESLint 默认使用 [Espree](https://github.com/eslint/espree) 进行语法解析，无法识别 TypeScript 的一些语法，故我们需要安装 [`@typescript-eslint/parser`](https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/parser)，替代掉默认的解析器，别忘了同时安装 `typescript`：\n\n```bash\nnpm install --save-dev typescript @typescript-eslint/parser\n```\n\n接下来需要安装对应的插件 [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin) 它作为 eslint 默认规则的补充，提供了一些额外的适用于 ts 语法的规则。\n\n```bash\nnpm install --save-dev @typescript-eslint/eslint-plugin\n```\n\n### 创建配置文件\n\nESLint 需要一个配置文件来决定对哪些规则进行检查，配置文件的名称一般是 `.eslintrc.js` 或 `.eslintrc.json`。\n\n当运行 ESLint 的时候检查一个文件的时候，它会首先尝试读取该文件的目录下的配置文件，然后再一级一级往上查找，将所找到的配置合并起来，作为当前被检查文件的配置。\n\n我们在项目的根目录下创建一个 `.eslintrc.js`，内容如下：\n\n```js\nmodule.exports = {\n    parser: '@typescript-eslint/parser',\n    plugins: ['@typescript-eslint'],\n    rules: {\n        // 禁止使用 var\n        'no-var': \"error\",\n        // 优先使用 interface 而不是 type\n        '@typescript-eslint/consistent-type-definitions': [\n            \"error\",\n            \"interface\"\n        ]\n    }\n}\n```\n\n以上配置中，我们指定了两个规则，其中 `no-var` 是 ESLint 原生的规则，`@typescript-eslint/consistent-type-definitions` 是 `@typescript-eslint/eslint-plugin` 新增的规则。\n\n规则的取值一般是一个数组（上例中的 `@typescript-eslint/consistent-type-definitions`），其中第一项是 `off`、`warn` 或 `error` 中的一个，表示关闭、警告和报错。后面的项都是该规则的其他配置。\n\n如果没有其他配置的话，则可以将规则的取值简写为数组中的第一项（上例中的 `no-var`）。\n\n关闭、警告和报错的含义如下：\n\n- 关闭：禁用此规则\n- 警告：代码检查时输出错误信息，但是不会影响到 exit code\n- 报错：发现错误时，不仅会输出错误信息，而且 exit code 将被设为 1（一般 exit code 不为 0 则表示执行出现错误）\n\n### 检查一个 ts 文件\n\n创建了配置文件之后，我们来创建一个 ts 文件看看是否能用 ESLint 去检查它。\n\n创建一个新文件 `index.ts`，将以下内容复制进去：\n\n```ts\nvar myName = 'Tom';\n\ntype Foo = {};\n```\n\n然后执行以下命令：\n\n```bash\n./node_modules/.bin/eslint index.ts\n```\n\n则会得到如下报错信息：\n\n```bash\n/path/to/index.ts\n  1:1  error  Unexpected var, use let or const instead  no-var\n  3:6  error  Use an `interface` instead of a `type`    @typescript-eslint/consistent-type-definitions\n\n✖ 2 problems (2 errors, 0 warnings)\n  2 errors and 0 warnings potentially fixable with the `--fix` option.\n```\n\n上面的结果显示，刚刚配置的两个规则都生效了：禁止使用 `var`；优先使用 `interface` 而不是 `type`。\n\n需要注意的是，我们使用的是 `./node_modules/.bin/eslint`，而不是全局的 `eslint` 脚本，这是因为代码检查是项目的重要组成部分，所以我们一般会将它安装在当前项目中。\n\n可是每次执行这么长一段脚本颇有不便，我们可以通过在 `package.json` 中添加一个 `script` 来创建一个 npm script 来简化这个步骤：\n\n```json\n{\n    \"scripts\": {\n        \"eslint\": \"eslint index.ts\"\n    }\n}\n```\n\n这时只需执行 `npm run eslint` 即可。\n\n### 检查整个项目的 ts 文件\n\n我们的项目源文件一般是放在 `src` 目录下，所以需要将 `package.json` 中的 `eslint` 脚本改为对一个目录进行检查。由于 `eslint` 默认不会检查 `.ts` 后缀的文件，所以需要加上参数 `--ext .ts`：\n\n```json\n{\n    \"scripts\": {\n        \"eslint\": \"eslint src --ext .ts\"\n    }\n}\n```\n\n此时执行 `npm run eslint` 即会检查 `src` 目录下的所有 `.ts` 后缀的文件。\n\n### 在 VSCode 中集成 ESLint 检查\n\n在编辑器中集成 ESLint 检查，可以在开发过程中就发现错误，甚至可以在保存时自动修复错误，极大的增加了开发效率。\n\n要在 VSCode 中集成 ESLint 检查，我们需要先安装 ESLint 插件，点击「扩展」按钮，搜索 ESLint，然后安装即可。\n\n通过配置 VSCode，可以开启保存时自动修复的功能：\n\n```json\n{\n    \"eslint.autoFixOnSave\": true,\n    \"eslint.validate\": [\n        \"javascript\",\n        \"javascriptreact\",\n        {\n            \"language\": \"typescript\",\n            \"autoFix\": true\n        },\n    ],\n    \"typescript.tsdk\": \"node_modules/typescript/lib\"\n}\n```\n\n就可以在保存文件后，自动修复为：\n\n```ts\nlet myName = 'Tom';\n\ninterface Foo {}\n```\n\n### 使用 Prettier 修复格式错误\n\nESLint 包含了一些代码格式的检查，比如空格、分号等。但前端社区中有一个更先进的工具可以用来格式化代码，那就是 [Prettier](https://prettier.io/)。\n\nPrettier 聚焦于代码的格式化，通过语法分析，重新整理代码的格式，让所有人的代码都保持同样的风格。\n\n首先需要安装 Prettier：\n\n```bash\nnpm install --save-dev prettier\n```\n\n然后创建一个 `prettier.config.js` 文件，里面包含 Prettier 的配置项。Prettier 的配置项很少，这里我推荐大家一个配置规则，作为参考：\n\n```js\n// prettier.config.js or .prettierrc.js\nmodule.exports = {\n    // 一行最多 100 字符\n    printWidth: 100,\n    // 使用 4 个空格缩进\n    tabWidth: 4,\n    // 不使用缩进符，而使用空格\n    useTabs: false,\n    // 行尾需要有分号\n    semi: true,\n    // 使用单引号\n    singleQuote: true,\n    // 对象的 key 仅在必要时用引号\n    quoteProps: 'as-needed',\n    // jsx 不使用单引号，而使用双引号\n    jsxSingleQuote: false,\n    // 末尾不需要逗号\n    trailingComma: 'none',\n    // 大括号内的首尾需要空格\n    bracketSpacing: true,\n    // jsx 标签的反尖括号需要换行\n    jsxBracketSameLine: false,\n    // 箭头函数，只有一个参数的时候，也需要括号\n    arrowParens: 'always',\n    // 每个文件格式化的范围是文件的全部内容\n    rangeStart: 0,\n    rangeEnd: Infinity,\n    // 不需要写文件开头的 @prettier\n    requirePragma: false,\n    // 不需要自动在文件开头插入 @prettier\n    insertPragma: false,\n    // 使用默认的折行标准\n    proseWrap: 'preserve',\n    // 根据显示样式决定 html 要不要折行\n    htmlWhitespaceSensitivity: 'css',\n    // 换行符使用 lf\n    endOfLine: 'lf'\n};\n```\n\n接下来安装 VSCode 中的 Prettier 插件，然后修改 `.vscode/settings.json`：\n\n```json\n{\n    \"files.eol\": \"\\n\",\n    \"editor.tabSize\": 4,\n    \"editor.formatOnSave\": true,\n    \"editor.defaultFormatter\": \"esbenp.prettier-vscode\",\n    \"eslint.autoFixOnSave\": true,\n    \"eslint.validate\": [\n        \"javascript\",\n        \"javascriptreact\",\n        {\n            \"language\": \"typescript\",\n            \"autoFix\": true\n        }\n    ],\n    \"typescript.tsdk\": \"node_modules/typescript/lib\"\n}\n```\n\n这样就实现了保存文件时自动格式化并且自动修复 ESLint 错误。\n\n需要注意的是，由于 ESLint 也可以检查一些代码格式的问题，所以在和 Prettier 配合使用时，我们一般会把 ESLint 中的代码格式相关的规则禁用掉，否则就会有冲突了。\n\n### 使用 AlloyTeam 的 ESLint 配置\n\nESLint 原生的规则和 `@typescript-eslint/eslint-plugin` 的规则太多了，而且原生的规则有一些在 TypeScript 中支持的不好，需要禁用掉。\n\n这里我推荐使用 [AlloyTeam ESLint 规则中的 TypeScript 版本](https://github.com/AlloyTeam/eslint-config-alloy#typescript)，它已经为我们提供了一套完善的配置规则，并且与 Prettier 是完全兼容的（eslint-config-alloy 不包含任何代码格式的规则，代码格式的问题交给更专业的 Prettier 去处理）。\n\n安装：\n\n```bash\nnpm install --save-dev eslint typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-config-alloy\n```\n\n在你的项目根目录下创建 `.eslintrc.js`，并将以下内容复制到文件中即可：\n\n```js\nmodule.exports = {\n    extends: [\n        'alloy',\n        'alloy/typescript',\n    ],\n    env: {\n        // 您的环境变量（包含多个预定义的全局变量）\n        // Your environments (which contains several predefined global variables)\n        //\n        // browser: true,\n        // node: true,\n        // mocha: true,\n        // jest: true,\n        // jquery: true\n    },\n    globals: {\n        // 您的全局变量（设置为 false 表示它不允许被重新赋值）\n        // Your global variables (setting to false means it's not allowed to be reassigned)\n        //\n        // myGlobal: false\n    },\n    rules: {\n        // 自定义您的规则\n        // Customize your rules\n    }\n};\n```\n\n更多的使用方法，请参考 [AlloyTeam ESLint 规则](https://github.com/AlloyTeam/eslint-config-alloy)\n\n### 使用 ESLint 检查 tsx 文件\n\n如果需要同时支持对 tsx 文件的检查，则需要对以上步骤做一些调整：\n\n#### 安装 `eslint-plugin-react`\n\n```bash\nnpm install --save-dev eslint-plugin-react\n```\n\n#### package.json 中的 scripts.eslint 添加 `.tsx` 后缀\n\n```json\n{\n    \"scripts\": {\n        \"eslint\": \"eslint src --ext .ts,.tsx\"\n    }\n}\n```\n\n#### VSCode 的配置中新增 typescriptreact 检查\n\n```json\n{\n    \"files.eol\": \"\\\\n\",\n    \"editor.tabSize\": 4,\n    \"editor.formatOnSave\": true,\n    \"editor.defaultFormatter\": \"esbenp.prettier-vscode\",\n    \"eslint.autoFixOnSave\": true,\n    \"eslint.validate\": [\n        \"javascript\",\n        \"javascriptreact\",\n        {\n            \"language\": \"typescript\",\n            \"autoFix\": true\n        },\n        {\n            \"language\": \"typescriptreact\",\n            \"autoFix\": true\n        }\n    ],\n    \"typescript.tsdk\": \"node_modules/typescript/lib\"\n}\n```\n\n#### 使用 AlloyTeam ESLint 规则中的 TypeScript React 版本\n\n[AlloyTeam ESLint 规则中的 TypeScript React 版本](https://github.com/AlloyTeam/eslint-config-alloy#typescript-react)\n\n## Troubleshootings\n\n### Cannot find module '@typescript-eslint/parser'\n\n你运行的是全局的 eslint，需要改为运行 `./node_modules/.bin/eslint`。\n\n### VSCode 没有显示出 ESLint 的报错\n\n1. 检查「文件 => 首选项 => 设置」中有没有配置正确\n2. 检查必要的 npm 包有没有安装\n3. 检查 `.eslintrc.js` 有没有配置\n4. 检查文件是不是在 `.eslintignore` 中\n\n如果以上步骤都不奏效，则可以在「文件 => 首选项 => 设置」中配置 `\"eslint.trace.server\": \"messages\"`，按 `Ctrl`+`Shift`+`U` 打开输出面板，然后选择 ESLint 输出，查看具体错误。\n\n![VSCode 的 ESLint 输出](../assets/vscode-output-eslint.png)\n\n### 为什么有些定义了的变量（比如使用 `enum` 定义的变量）未使用，ESLint 却没有报错？\n\n因为无法支持这种变量定义的检查。建议在 `tsconfig.json` 中添加以下配置，使 `tsc` 编译过程能够检查出定义了未使用的变量：\n\n```json\n{\n    \"compilerOptions\": {\n        \"noUnusedLocals\": true,\n        \"noUnusedParameters\": true\n    }\n}\n```\n\n### 启用了 noUnusedParameters 之后，只使用了第二个参数，但是又必须传入第一个参数，这就会报错了\n\n第一个参数以下划线开头即可，参考 https://github.com/Microsoft/TypeScript/issues/9458\n\n[ESLint]: https://eslint.org/\n[typescript-eslint]: https://github.com/typescript-eslint/typescript-eslint\n"
  },
  {
    "path": "examples/compiler-options/01-allowJs/false/lib/index.js",
    "content": "\"use strict\";\nexports.__esModule = true;\nvar foo_1 = require(\"./foo\");\nconsole.log(foo_1[\"default\"]);\n"
  },
  {
    "path": "examples/compiler-options/01-allowJs/false/package.json",
    "content": "{\n    \"name\": \"01-allow-js-false\",\n    \"version\": \"1.0.0\",\n    \"description\": \"\",\n    \"main\": \"index.js\",\n    \"scripts\": {\n        \"start\": \"tsc -w\",\n        \"build\": \"tsc\",\n        \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n    },\n    \"author\": \"\",\n    \"license\": \"MIT\",\n    \"dependencies\": {},\n    \"devDependencies\": {}\n}\n"
  },
  {
    "path": "examples/compiler-options/01-allowJs/false/src/foo.js",
    "content": "const foo = 1;\nexport default foo;\n"
  },
  {
    "path": "examples/compiler-options/01-allowJs/false/src/index.ts",
    "content": "import foo from './foo';\nconsole.log(foo);\n"
  },
  {
    "path": "examples/compiler-options/01-allowJs/false/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"allowJs\": false,\n        \"outDir\": \"lib\"\n    }\n}\n"
  },
  {
    "path": "examples/compiler-options/01-allowJs/true/lib/foo.js",
    "content": "\"use strict\";\nexports.__esModule = true;\nvar foo = 1;\nexports[\"default\"] = foo;\n"
  },
  {
    "path": "examples/compiler-options/01-allowJs/true/lib/index.js",
    "content": "\"use strict\";\nexports.__esModule = true;\nvar foo_1 = require(\"./foo\");\nconsole.log(foo_1[\"default\"]);\n"
  },
  {
    "path": "examples/compiler-options/01-allowJs/true/package.json",
    "content": "{\n    \"name\": \"01-allow-js-true\",\n    \"version\": \"1.0.0\",\n    \"description\": \"\",\n    \"main\": \"index.js\",\n    \"scripts\": {\n        \"start\": \"tsc -w\",\n        \"build\": \"tsc\",\n        \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n    },\n    \"author\": \"\",\n    \"license\": \"MIT\",\n    \"dependencies\": {},\n    \"devDependencies\": {}\n}\n"
  },
  {
    "path": "examples/compiler-options/01-allowJs/true/src/foo.js",
    "content": "const foo = 1;\nexport default foo;\n"
  },
  {
    "path": "examples/compiler-options/01-allowJs/true/src/index.ts",
    "content": "import foo from './foo';\nconsole.log(foo);\n"
  },
  {
    "path": "examples/compiler-options/01-allowJs/true/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"allowJs\": true,\n        \"outDir\": \"lib\"\n    }\n}\n"
  },
  {
    "path": "examples/compiler-options/02-allowSyntheticDefaultImports/false/package.json",
    "content": "{\n    \"name\": \"02-allow-synthetic-default-imports-false\",\n    \"version\": \"1.0.0\",\n    \"description\": \"\",\n    \"main\": \"index.js\",\n    \"scripts\": {\n        \"start\": \"tsc -w\",\n        \"build\": \"tsc\",\n        \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n    },\n    \"author\": \"\",\n    \"license\": \"MIT\",\n    \"dependencies\": {\n        \"react\": \"^16.12.0\"\n    },\n    \"devDependencies\": {\n        \"@types/react\": \"^16.9.14\"\n    }\n}\n"
  },
  {
    "path": "examples/compiler-options/02-allowSyntheticDefaultImports/false/src/index.ts",
    "content": "import React from 'react';\n"
  },
  {
    "path": "examples/compiler-options/02-allowSyntheticDefaultImports/false/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"allowSyntheticDefaultImports\": false,\n        \"outDir\": \"lib\"\n    }\n}\n"
  },
  {
    "path": "examples/compiler-options/02-allowSyntheticDefaultImports/true/package.json",
    "content": "{\n    \"name\": \"02-allow-synthetic-default-imports-false\",\n    \"version\": \"1.0.0\",\n    \"description\": \"\",\n    \"main\": \"index.js\",\n    \"scripts\": {\n        \"start\": \"tsc -w\",\n        \"build\": \"tsc\",\n        \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n    },\n    \"author\": \"\",\n    \"license\": \"MIT\",\n    \"dependencies\": {\n        \"react\": \"^16.12.0\"\n    },\n    \"devDependencies\": {\n        \"@types/react\": \"^16.9.14\"\n    }\n}\n"
  },
  {
    "path": "examples/compiler-options/02-allowSyntheticDefaultImports/true/src/index.ts",
    "content": "import React from 'react';\n"
  },
  {
    "path": "examples/compiler-options/02-allowSyntheticDefaultImports/true/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"allowSyntheticDefaultImports\": true,\n        \"outDir\": \"lib\"\n    }\n}\n"
  },
  {
    "path": "examples/declaration-files/01-jquery/src/index.ts",
    "content": "jQuery('#foo');\n// ERROR: Cannot find name 'jQuery'.\n"
  },
  {
    "path": "examples/declaration-files/01-jquery/tsconfig.json",
    "content": "{}\n"
  },
  {
    "path": "examples/declaration-files/02-declare-var/src/index.ts",
    "content": "// eslint-disable-next-line no-var\ndeclare var jQuery: (selector: string) => any;\n\njQuery('#foo');\n"
  },
  {
    "path": "examples/declaration-files/02-declare-var/tsconfig.json",
    "content": "{}\n"
  },
  {
    "path": "examples/declaration-files/03-jquery-d-ts/src/index.ts",
    "content": "// src/index.ts\n\njQuery('#foo');\n"
  },
  {
    "path": "examples/declaration-files/03-jquery-d-ts/src/jQuery.d.ts",
    "content": "// src/jQuery.d.ts\n\ndeclare var jQuery: (selector: string) => any;\n"
  },
  {
    "path": "examples/declaration-files/03-jquery-d-ts/tsconfig.json",
    "content": "{\n    \"files\": [\"src/index.ts\", \"src/jQuery.d.ts\"]\n}\n"
  },
  {
    "path": "examples/declaration-files/04-declare-const-jquery/src/index.ts",
    "content": "// src/index.ts\n\njQuery('#foo');\n// 使用 declare const 定义的 jQuery 类型，禁止修改这个全局变量\njQuery = function(selector) {\n    return document.querySelector(selector);\n};\n// ERROR: Cannot assign to 'jQuery' because it is a constant or a read-only property.\n"
  },
  {
    "path": "examples/declaration-files/04-declare-const-jquery/src/jQuery.d.ts",
    "content": "// src/jQuery.d.ts\n\ndeclare const jQuery: (selector: string) => any;\n"
  },
  {
    "path": "examples/declaration-files/04-declare-const-jquery/tsconfig.json",
    "content": "{}\n"
  },
  {
    "path": "examples/declaration-files/05-declare-jquery-value/src/jQuery.d.ts",
    "content": "declare const jQuery = function(selector) {\n    return document.querySelector(selector);\n};\n// ERROR: An implementation cannot be declared in ambient contexts.\n"
  },
  {
    "path": "examples/declaration-files/05-declare-jquery-value/tsconfig.json",
    "content": "{}\n"
  },
  {
    "path": "examples/declaration-files/06-declare-function/src/index.ts",
    "content": "// src/index.ts\n\njQuery('#foo');\njQuery(function() {\n    alert('Dom Ready!');\n});\n"
  },
  {
    "path": "examples/declaration-files/06-declare-function/src/jQuery.d.ts",
    "content": "// src/jQuery.d.ts\n\ndeclare function jQuery(selector: string): any;\ndeclare function jQuery(domReadyCallback: () => any): any;\n"
  },
  {
    "path": "examples/declaration-files/06-declare-function/tsconfig.json",
    "content": "{}\n"
  },
  {
    "path": "examples/declaration-files/07-declare-class/src/Animal.d.ts",
    "content": "// src/Animal.d.ts\n\ndeclare class Animal {\n    name: string;\n    constructor(name: string);\n    sayHi(): string;\n}\n"
  },
  {
    "path": "examples/declaration-files/07-declare-class/src/index.ts",
    "content": "// src/index.ts\n\nlet cat = new Animal('Tom');\n"
  },
  {
    "path": "examples/declaration-files/07-declare-class/tsconfig.json",
    "content": "{}\n"
  },
  {
    "path": "examples/declaration-files/08-declare-enum/src/Directions.d.ts",
    "content": "// src/Directions.d.ts\n\ndeclare enum Directions {\n    Up,\n    Down,\n    Left,\n    Right\n}\n"
  },
  {
    "path": "examples/declaration-files/08-declare-enum/src/index.ts",
    "content": "// src/index.ts\n\nlet directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];\n"
  },
  {
    "path": "examples/declaration-files/08-declare-enum/tsconfig.json",
    "content": "{}\n"
  },
  {
    "path": "examples/declaration-files/09-declare-namespace/src/index.ts",
    "content": "// src/index.ts\n\njQuery.ajax('/api/get_something');\nconsole.log(jQuery.version);\nconst e = new jQuery.Event();\ne.blur(jQuery.EventType.CustomClick);\n"
  },
  {
    "path": "examples/declaration-files/09-declare-namespace/src/jQuery.d.ts",
    "content": "// src/jQuery.d.ts\n\ndeclare namespace jQuery {\n    function ajax(url: string, settings?: any): void;\n    const version: number;\n    class Event {\n        blur(eventType: EventType): void;\n    }\n    enum EventType {\n        CustomClick\n    }\n}\n"
  },
  {
    "path": "examples/declaration-files/09-declare-namespace/tsconfig.json",
    "content": "{}\n"
  },
  {
    "path": "examples/declaration-files/10-declare-namespace-nesting/src/index.ts",
    "content": "// src/index.ts\n\njQuery.ajax('/api/get_something');\njQuery.fn.extend({\n    check: function() {\n        return this.each(function() {\n            this.checked = true;\n        });\n    }\n});\n"
  },
  {
    "path": "examples/declaration-files/10-declare-namespace-nesting/src/jQuery.d.ts",
    "content": "// src/jQuery.d.ts\n\ndeclare namespace jQuery {\n    function ajax(url: string, settings?: any): void;\n    namespace fn {\n        function extend(object: any): void;\n    }\n}\n"
  },
  {
    "path": "examples/declaration-files/10-declare-namespace-nesting/tsconfig.json",
    "content": "{}\n"
  },
  {
    "path": "examples/declaration-files/11-declare-namespace-dot/src/index.ts",
    "content": "// src/index.ts\n\njQuery.fn.extend({\n    check: function() {\n        return this.each(function() {\n            this.checked = true;\n        });\n    }\n});\n"
  },
  {
    "path": "examples/declaration-files/11-declare-namespace-dot/src/jQuery.d.ts",
    "content": "// src/jQuery.d.ts\n\ndeclare namespace jQuery.fn {\n    function extend(object: any): void;\n}\n"
  },
  {
    "path": "examples/declaration-files/11-declare-namespace-dot/tsconfig.json",
    "content": "{}\n"
  },
  {
    "path": "examples/declaration-files/12-interface/src/index.ts",
    "content": "// src/index.ts\n\nlet settings: AjaxSettings = {\n    method: 'POST',\n    data: {\n        name: 'foo'\n    }\n};\njQuery.ajax('/api/post_something', settings);\n"
  },
  {
    "path": "examples/declaration-files/12-interface/src/jQuery.d.ts",
    "content": "// src/jQuery.d.ts\n\ninterface AjaxSettings {\n    method?: 'GET' | 'POST';\n    data?: any;\n}\ndeclare namespace jQuery {\n    function ajax(url: string, settings?: AjaxSettings): void;\n}\n"
  },
  {
    "path": "examples/declaration-files/12-interface/tsconfig.json",
    "content": "{}\n"
  },
  {
    "path": "examples/declaration-files/13-avoid-name-conflict/src/index.ts",
    "content": "// src/index.ts\n\nlet settings: jQuery.AjaxSettings = {\n    method: 'POST',\n    data: {\n        name: 'foo'\n    }\n};\njQuery.ajax('/api/post_something', settings);\n"
  },
  {
    "path": "examples/declaration-files/13-avoid-name-conflict/src/jQuery.d.ts",
    "content": "// src/jQuery.d.ts\n\ndeclare namespace jQuery {\n    interface AjaxSettings {\n        method?: 'GET' | 'POST';\n        data?: any;\n    }\n    function ajax(url: string, settings?: AjaxSettings): void;\n}\n"
  },
  {
    "path": "examples/declaration-files/13-avoid-name-conflict/tsconfig.json",
    "content": "{}\n"
  },
  {
    "path": "examples/declaration-files/14-declaration-merging/src/index.ts",
    "content": "// src/index.ts\n\njQuery('#foo');\njQuery.ajax('/api/get_something');\n"
  },
  {
    "path": "examples/declaration-files/14-declaration-merging/src/jQuery.d.ts",
    "content": "// src/jQuery.d.ts\n\ndeclare function jQuery(selector: string): any;\ndeclare namespace jQuery {\n    function ajax(url: string, settings?: any): void;\n}\n"
  },
  {
    "path": "examples/declaration-files/14-declaration-merging/tsconfig.json",
    "content": "{}\n"
  },
  {
    "path": "examples/declaration-files/15-export/src/index.ts",
    "content": "// src/index.ts\n\nimport { name, getName, Animal, Directions, Options } from 'foo';\n\nconsole.log(name);\nlet myName = getName();\nlet cat = new Animal('Tom');\nlet directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];\nlet options: Options = {\n    data: {\n        name: 'foo'\n    }\n};\n"
  },
  {
    "path": "examples/declaration-files/15-export/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"module\": \"commonjs\",\n        \"baseUrl\": \"./\",\n        \"paths\": {\n            \"*\": [\"types/*\"]\n        }\n    }\n}\n"
  },
  {
    "path": "examples/declaration-files/15-export/types/foo/index.d.ts",
    "content": "// types/foo/index.d.ts\n\nexport const name: string;\nexport function getName(): string;\nexport class Animal {\n    constructor(name: string);\n    sayHi(): string;\n}\nexport enum Directions {\n    Up,\n    Down,\n    Left,\n    Right\n}\nexport interface Options {\n    data: any;\n}\n"
  },
  {
    "path": "examples/declaration-files/16-declare-and-export/src/index.ts",
    "content": "// src/index.ts\n\nimport { name, getName, Animal, Directions, Options } from 'foo';\n\nconsole.log(name);\nlet myName = getName();\nlet cat = new Animal('Tom');\nlet directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];\nlet options: Options = {\n    data: {\n        name: 'foo'\n    }\n};\n"
  },
  {
    "path": "examples/declaration-files/16-declare-and-export/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"module\": \"commonjs\",\n        \"baseUrl\": \"./\",\n        \"paths\": {\n            \"*\": [\"types/*\"]\n        }\n    }\n}\n"
  },
  {
    "path": "examples/declaration-files/16-declare-and-export/types/foo/index.d.ts",
    "content": "// types/foo/index.d.ts\n\ndeclare const name: string;\ndeclare function getName(): string;\ndeclare class Animal {\n    constructor(name: string);\n    sayHi(): string;\n}\ndeclare enum Directions {\n    Up,\n    Down,\n    Left,\n    Right\n}\ninterface Options {\n    data: any;\n}\n\nexport { name, getName, Animal, Directions, Options };\n"
  },
  {
    "path": "examples/declaration-files/17-export-namespace/src/index.ts",
    "content": "// src/index.ts\n\nimport { foo } from 'foo';\n\nconsole.log(foo.name);\nfoo.bar.baz();\n"
  },
  {
    "path": "examples/declaration-files/17-export-namespace/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"module\": \"commonjs\",\n        \"baseUrl\": \"./\",\n        \"paths\": {\n            \"*\": [\"types/*\"]\n        }\n    }\n}\n"
  },
  {
    "path": "examples/declaration-files/17-export-namespace/types/foo/index.d.ts",
    "content": "// types/foo/index.d.ts\n\nexport namespace foo {\n    const name: string;\n    namespace bar {\n        function baz(): string;\n    }\n}\n"
  },
  {
    "path": "examples/declaration-files/18-export-default/src/index.ts",
    "content": "// src/index.ts\n\nimport foo from 'foo';\n\nfoo();\n"
  },
  {
    "path": "examples/declaration-files/18-export-default/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"module\": \"commonjs\",\n        \"baseUrl\": \"./\",\n        \"paths\": {\n            \"*\": [\"types/*\"]\n        }\n    }\n}\n"
  },
  {
    "path": "examples/declaration-files/18-export-default/types/foo/index.d.ts",
    "content": "// types/foo/index.d.ts\n\nexport default function foo(): string;\n"
  },
  {
    "path": "examples/declaration-files/19-export-default-enum-error/src/index.ts",
    "content": "// src/index.ts\n\nimport foo from 'foo';\n\nconsole.log(foo.Down);\n"
  },
  {
    "path": "examples/declaration-files/19-export-default-enum-error/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"module\": \"commonjs\",\n        \"baseUrl\": \"./\",\n        \"paths\": {\n            \"*\": [\"types/*\"]\n        }\n    }\n}\n"
  },
  {
    "path": "examples/declaration-files/19-export-default-enum-error/types/foo/index.d.ts",
    "content": "// types/foo/index.d.ts\n\nexport default enum Directions {\n// ERROR: Expression expected.\n    Up,\n    Down,\n    Left,\n    Right\n}\n"
  },
  {
    "path": "examples/declaration-files/20-export-default-enum/src/index.ts",
    "content": "// src/index.ts\n\nimport foo from 'foo';\n\nconsole.log(foo.Down);\n"
  },
  {
    "path": "examples/declaration-files/20-export-default-enum/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"module\": \"commonjs\",\n        \"baseUrl\": \"./\",\n        \"paths\": {\n            \"*\": [\"types/*\"]\n        }\n    }\n}\n"
  },
  {
    "path": "examples/declaration-files/20-export-default-enum/types/foo/index.d.ts",
    "content": "// types/foo/index.d.ts\n\nexport default Directions;\n\ndeclare enum Directions {\n    Up,\n    Down,\n    Left,\n    Right\n}\n"
  },
  {
    "path": "examples/declaration-files/21-export-equal/src/index.ts",
    "content": "// 整体导入\nimport foo = require('foo');\n// 单个导入\nimport bar = foo.bar;\n"
  },
  {
    "path": "examples/declaration-files/21-export-equal/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"module\": \"commonjs\",\n        \"baseUrl\": \"./\",\n        \"paths\": {\n            \"*\": [\"types/*\"]\n        }\n    }\n}\n"
  },
  {
    "path": "examples/declaration-files/21-export-equal/types/foo/index.d.ts",
    "content": "// types/foo/index.d.ts\n\nexport = foo;\n\ndeclare function foo(): string;\ndeclare namespace foo {\n    const bar: number;\n}\n"
  },
  {
    "path": "examples/declaration-files/22-export-as-namespace/src/index.ts",
    "content": "// src/index.ts\n\nfoo();\nconsole.log(foo.bar);\n"
  },
  {
    "path": "examples/declaration-files/22-export-as-namespace/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"module\": \"commonjs\",\n        \"baseUrl\": \"./\",\n        \"paths\": {\n            \"*\": [\"types/*\"]\n        }\n    }\n}\n"
  },
  {
    "path": "examples/declaration-files/22-export-as-namespace/types/foo/index.d.ts",
    "content": "// types/foo/index.d.ts\n\nexport as namespace foo;\nexport = foo;\n\ndeclare function foo(): string;\ndeclare namespace foo {\n    const bar: number;\n}\n"
  },
  {
    "path": "examples/declaration-files/23-merge-global-interface/src/index.ts",
    "content": "interface String {\n    prependHello(): string;\n}\n\n'foo'.prependHello();\n"
  },
  {
    "path": "examples/declaration-files/23-merge-global-interface/tsconfig.json",
    "content": "{}\n"
  },
  {
    "path": "examples/declaration-files/24-merge-global-namespace/package.json",
    "content": "{\n    \"name\": \"24-merge-global-namespace\",\n    \"version\": \"1.0.0\",\n    \"description\": \"\",\n    \"main\": \"index.js\",\n    \"scripts\": {\n        \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n    },\n    \"author\": \"\",\n    \"license\": \"MIT\",\n    \"dependencies\": {\n        \"jquery\": \"^3.5.0\"\n    },\n    \"devDependencies\": {\n        \"@types/jquery\": \"^3.3.29\"\n    }\n}\n"
  },
  {
    "path": "examples/declaration-files/24-merge-global-namespace/src/index.ts",
    "content": "// src/index.ts\n\njQuery.foo({\n    bar: ''\n});\n"
  },
  {
    "path": "examples/declaration-files/24-merge-global-namespace/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"module\": \"commonjs\",\n        \"baseUrl\": \"./\",\n        \"paths\": {\n            \"*\": [\"types/*\"]\n        }\n    }\n}\n"
  },
  {
    "path": "examples/declaration-files/24-merge-global-namespace/types/jquery-plugin/index.d.ts",
    "content": "// types/jquery-plugin/index.d.ts\n\ndeclare namespace JQuery {\n    interface CustomOptions {\n        bar: string;\n    }\n}\n\ninterface JQueryStatic {\n    foo(options: JQuery.CustomOptions): string;\n}\n"
  },
  {
    "path": "examples/declaration-files/25-declare-global/src/index.ts",
    "content": "// src/index.ts\n\n'foo'.prependHello();\n"
  },
  {
    "path": "examples/declaration-files/25-declare-global/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"module\": \"commonjs\",\n        \"baseUrl\": \"./\",\n        \"paths\": {\n            \"*\": [\"types/*\"]\n        }\n    }\n}\n"
  },
  {
    "path": "examples/declaration-files/25-declare-global/types/foo/index.d.ts",
    "content": "// types/foo/index.d.ts\n\ndeclare global {\n    interface String {\n        prependHello(): string;\n    }\n}\n\nexport {};\n"
  },
  {
    "path": "examples/declaration-files/26-declare-module/package.json",
    "content": "{\n    \"name\": \"26-declare-module\",\n    \"version\": \"1.0.0\",\n    \"description\": \"\",\n    \"main\": \"index.js\",\n    \"scripts\": {\n        \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n    },\n    \"author\": \"\",\n    \"license\": \"MIT\",\n    \"dependencies\": {\n        \"moment\": \"^2.24.0\"\n    },\n    \"devDependencies\": {}\n}\n"
  },
  {
    "path": "examples/declaration-files/26-declare-module/src/index.ts",
    "content": "// src/index.ts\n\nimport * as moment from 'moment';\nimport 'moment-plugin';\n\nmoment.foo();\n"
  },
  {
    "path": "examples/declaration-files/26-declare-module/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"module\": \"commonjs\",\n        \"baseUrl\": \"./\",\n        \"paths\": {\n            \"*\": [\"types/*\"]\n        }\n    }\n}\n"
  },
  {
    "path": "examples/declaration-files/26-declare-module/types/moment-plugin/index.d.ts",
    "content": "// types/moment-plugin/index.d.ts\n\nimport * as moment from 'moment';\n\ndeclare module 'moment' {\n    export function foo(): moment.CalendarKey;\n}\n"
  },
  {
    "path": "examples/declaration-files/27-multiple-declare-module/src/index.ts",
    "content": "// src/index.ts\n\nimport { Foo } from 'foo';\nimport * as bar from 'bar';\n\nlet f: Foo;\nbar.bar();\n"
  },
  {
    "path": "examples/declaration-files/27-multiple-declare-module/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"module\": \"commonjs\",\n        \"baseUrl\": \"./\",\n        \"paths\": {\n            \"*\": [\"types/*\"]\n        }\n    }\n}\n"
  },
  {
    "path": "examples/declaration-files/27-multiple-declare-module/types/foo-bar.d.ts",
    "content": "// types/foo-bar.d.ts\n\ndeclare module 'foo' {\n    export interface Foo {\n        foo: string;\n    }\n}\n\ndeclare module 'bar' {\n    export function bar(): string;\n}\n"
  },
  {
    "path": "examples/declaration-files/28-triple-slash-directives/package.json",
    "content": "{\n    \"name\": \"28-triple-slash-directives\",\n    \"version\": \"1.0.0\",\n    \"description\": \"\",\n    \"main\": \"index.js\",\n    \"scripts\": {\n        \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n    },\n    \"author\": \"\",\n    \"license\": \"MIT\",\n    \"dependencies\": {\n        \"jquery\": \"^3.5.0\"\n    },\n    \"devDependencies\": {\n        \"@types/jquery\": \"^3.3.29\"\n    }\n}\n"
  },
  {
    "path": "examples/declaration-files/28-triple-slash-directives/src/index.ts",
    "content": "// src/index.ts\n\nfoo({});\n"
  },
  {
    "path": "examples/declaration-files/28-triple-slash-directives/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"module\": \"commonjs\",\n        \"baseUrl\": \"./\",\n        \"paths\": {\n            \"*\": [\"types/*\"]\n        }\n    }\n}\n"
  },
  {
    "path": "examples/declaration-files/28-triple-slash-directives/types/jquery-plugin/index.d.ts",
    "content": "// types/jquery-plugin/index.d.ts\n\n/// <reference types=\"jquery\" />\n\ndeclare function foo(options: JQuery.AjaxSettings): string;\n"
  },
  {
    "path": "examples/declaration-files/29-triple-slash-directives-global/package.json",
    "content": "{\n    \"name\": \"29-triple-slash-directives-global\",\n    \"version\": \"1.0.0\",\n    \"description\": \"\",\n    \"main\": \"index.js\",\n    \"scripts\": {\n        \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n    },\n    \"author\": \"\",\n    \"license\": \"MIT\",\n    \"dependencies\": {},\n    \"devDependencies\": {\n        \"@types/node\": \"^12.0.1\"\n    }\n}\n"
  },
  {
    "path": "examples/declaration-files/29-triple-slash-directives-global/src/index.ts",
    "content": "// src/index.ts\n\nimport { foo } from 'node-plugin';\n\nfoo(global.process);\n"
  },
  {
    "path": "examples/declaration-files/29-triple-slash-directives-global/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"module\": \"commonjs\",\n        \"baseUrl\": \"./\",\n        \"paths\": {\n            \"*\": [\"types/*\"]\n        }\n    }\n}\n"
  },
  {
    "path": "examples/declaration-files/29-triple-slash-directives-global/types/node-plugin/index.d.ts",
    "content": "// types/node-plugin/index.d.ts\n\n/// <reference types=\"node\" />\n\nexport function foo(p: NodeJS.Process): string;\n"
  },
  {
    "path": "examples/declaration-files/30-auto-d-ts/lib/bar/index.d.ts",
    "content": "export declare function bar(): string;\n//# sourceMappingURL=index.d.ts.map\n"
  },
  {
    "path": "examples/declaration-files/30-auto-d-ts/lib/bar/index.js",
    "content": "\"use strict\";\nexports.__esModule = true;\nfunction bar() {\n    return 'bar';\n}\nexports.bar = bar;\n"
  },
  {
    "path": "examples/declaration-files/30-auto-d-ts/lib/index.d.ts",
    "content": "export * from './bar';\nexport default function foo(): string;\n//# sourceMappingURL=index.d.ts.map\n"
  },
  {
    "path": "examples/declaration-files/30-auto-d-ts/lib/index.js",
    "content": "\"use strict\";\nfunction __export(m) {\n    for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];\n}\nexports.__esModule = true;\n__export(require(\"./bar\"));\nfunction foo() {\n    return 'foo';\n}\nexports[\"default\"] = foo;\n"
  },
  {
    "path": "examples/declaration-files/30-auto-d-ts/package.json",
    "content": "{\n    \"name\": \"30-auto-d-ts\",\n    \"version\": \"1.0.0\",\n    \"description\": \"\",\n    \"main\": \"lib/index.js\",\n    \"scripts\": {\n        \"start\": \"tsc -w\",\n        \"build\": \"tsc\",\n        \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n    },\n    \"author\": \"\",\n    \"license\": \"MIT\",\n    \"devDependencies\": {\n        \"typescript\": \"^3.4.5\"\n    }\n}\n"
  },
  {
    "path": "examples/declaration-files/30-auto-d-ts/src/bar/index.ts",
    "content": "export function bar() {\n    return 'bar';\n}\n"
  },
  {
    "path": "examples/declaration-files/30-auto-d-ts/src/index.ts",
    "content": "export * from './bar';\n\nexport default function foo() {\n    return 'foo';\n}\n"
  },
  {
    "path": "examples/declaration-files/30-auto-d-ts/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"module\": \"commonjs\",\n        \"outDir\": \"lib\",\n        \"declaration\": true\n    }\n}\n"
  },
  {
    "path": "introduction/README.md",
    "content": "---\nprev: README.md\n---\n\n# 简介\n\n本部分介绍了在学习 TypeScript 之前需要了解的知识，具体内容包括：\n\n- [什么是 TypeScript](what-is-typescript.md)\n- [安装 TypeScript](get-typescript.md)\n- [Hello TypeScript](hello-typescript.md)\n"
  },
  {
    "path": "introduction/get-typescript.md",
    "content": "# 安装 TypeScript\n\nTypeScript 的命令行工具安装方法如下：\n\n```bash\nnpm install -g typescript\n```\n\n以上命令会在全局环境下安装 `tsc` 命令，安装完成之后，我们就可以在任何地方执行 `tsc` 命令了。\n\n编译一个 TypeScript 文件很简单：\n\n```bash\ntsc hello.ts\n```\n\n我们约定使用 TypeScript 编写的文件以 `.ts` 为后缀，用 TypeScript 编写 React 时，以 `.tsx` 为后缀。\n\n## 编辑器\n\nTypeScript 最大的优势之一便是增强了编辑器和 IDE 的功能，包括代码补全、接口提示、跳转到定义、重构等。\n\n主流的编辑器都支持 TypeScript，这里我推荐使用 [Visual Studio Code](https://code.visualstudio.com/)。\n\n它是一款开源，跨终端的轻量级编辑器，内置了对 TypeScript 的支持。\n\n另外它本身也是[用 TypeScript 编写的](https://github.com/Microsoft/vscode/)。\n\n下载安装：https://code.visualstudio.com/\n\n获取其他编辑器或 IDE 对 TypeScript 的支持：\n\n- [Sublime Text](https://github.com/Microsoft/TypeScript-Sublime-Plugin)\n- [WebStorm](https://www.jetbrains.com/webstorm/)\n- [Vim](https://github.com/Microsoft/TypeScript/wiki/TypeScript-Editor-Support#vim)\n- [Emacs](https://github.com/ananthakumaran/tide)\n- [Eclipse](https://github.com/palantir/eclipse-typescript)\n- [Atom](https://atom.io/packages/atom-typescript)\n- [Visual Studio 2019](https://marketplace.visualstudio.com/search?term=TypeScriptTeam&target=VS&category=All%20categories&vsVersion=vs2019&sortBy=UpdatedDate)\n- [Visual Studio 2017](https://marketplace.visualstudio.com/search?term=TypeScriptTeam&target=VS&category=All%20categories&vsVersion=vs15&sortBy=UpdatedDate)\n"
  },
  {
    "path": "introduction/hello-typescript.md",
    "content": "# Hello TypeScript\n\n我们从一个简单的例子开始。\n\n将以下代码复制到 `hello.ts` 中：\n\n```ts\nfunction sayHello(person: string) {\n    return 'Hello, ' + person;\n}\n\nlet user = 'Tom';\nconsole.log(sayHello(user));\n```\n\n然后执行\n\n```bash\ntsc hello.ts\n```\n\n这时候会生成一个编译好的文件 `hello.js`：\n\n```js\nfunction sayHello(person) {\n    return 'Hello, ' + person;\n}\nvar user = 'Tom';\nconsole.log(sayHello(user));\n```\n\n在 TypeScript 中，我们使用 `:` 指定变量的类型，`:` 的前后有没有空格都可以。\n\n上述例子中，我们用 `:` 指定 `person` 参数类型为 `string`。但是编译为 js 之后，并没有什么检查的代码被插入进来。\n\n这是因为 **TypeScript 只会在编译时对类型进行静态检查，如果发现有错误，编译的时候就会报错**。而在运行时，与普通的 JavaScript 文件一样，不会对类型进行检查。\n\n如果我们需要保证运行时的参数类型，还是得手动对类型进行判断：\n\n```ts\nfunction sayHello(person: string) {\n    if (typeof person === 'string') {\n        return 'Hello, ' + person;\n    } else {\n        throw new Error('person is not a string');\n    }\n}\n\nlet user = 'Tom';\nconsole.log(sayHello(user));\n```\n\n> `let` 是 ES6 中的关键字，和 `var` 类似，用于定义一个局部变量，可以参阅 [let 和 const 命令](http://es6.ruanyifeng.com/#docs/let)。\n\n下面尝试把这段代码编译一下：\n\n```ts\nfunction sayHello(person: string) {\n    return 'Hello, ' + person;\n}\n\nlet user = [0, 1, 2];\nconsole.log(sayHello(user));\n```\n\n编辑器中会提示错误，编译的时候也会出错：\n\n```bash\nhello.ts:6:22 - error TS2345: Argument of type 'number[]' is not assignable to parameter of type 'string'.\n```\n\n但是还是生成了 js 文件：\n\n```js\nfunction sayHello(person) {\n    return 'Hello, ' + person;\n}\nvar user = [0, 1, 2];\nconsole.log(sayHello(user));\n```\n\n这是因为 **TypeScript 编译的时候即使报错了，还是会生成编译结果**，我们仍然可以使用这个编译之后的文件。\n\n如果要在报错的时候终止 js 文件的生成，可以在 `tsconfig.json` 中配置 `noEmitOnError` 即可。关于 `tsconfig.json`，请参阅[官方手册](http://www.typescriptlang.org/docs/handbook/tsconfig-json.html)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/tsconfig.json.html)）。\n"
  },
  {
    "path": "introduction/what-is-typescript.md",
    "content": "# 什么是 TypeScript\n\n> Typed JavaScript at Any Scale.  \n> 添加了类型系统的 JavaScript，适用于任何规模的项目。\n\n以上描述是官网<sup>[[1]](#link-1)</sup>对于 TypeScript 的定义。\n\n它强调了 TypeScript 的两个最重要的特性——类型系统、适用于任何规模。\n\n## TypeScript 的特性\n\n### 类型系统\n\n从 TypeScript 的名字就可以看出来，「类型」是其最核心的特性。\n\n我们知道，JavaScript 是一门非常灵活的编程语言：\n\n- 它没有类型约束，一个变量可能初始化时是字符串，过一会儿又被赋值为数字。\n- 由于隐式类型转换的存在，有的变量的类型很难在运行前就确定。\n- 基于原型的面向对象编程，使得原型上的属性或方法可以在运行时被修改。\n- 函数是 JavaScript 中的一等公民<sup>[[2]](#link-2)</sup>，可以赋值给变量，也可以当作参数或返回值。\n\n这种灵活性就像一把双刃剑，一方面使得 JavaScript 蓬勃发展，无所不能，从 2013 年开始就一直蝉联最普遍使用的编程语言排行榜冠军<sup>[[3]](#link-3)</sup>；另一方面也使得它的代码质量参差不齐，维护成本高，运行时错误多。\n\n而 TypeScript 的类型系统，在很大程度上弥补了 JavaScript 的缺点。\n\n#### TypeScript 是静态类型\n\n类型系统按照「类型检查的时机」来分类，可以分为动态类型和静态类型。\n\n动态类型是指在运行时才会进行类型检查，这种语言的类型错误往往会导致运行时错误。JavaScript 是一门解释型语言<sup>[[4]](#link-4)</sup>，没有编译阶段，所以它是动态类型，以下这段代码在运行时才会报错：\n\n```js\nlet foo = 1;\nfoo.split(' ');\n// Uncaught TypeError: foo.split is not a function\n// 运行时会报错（foo.split 不是一个函数），造成线上 bug\n```\n\n静态类型是指编译阶段就能确定每个变量的类型，这种语言的类型错误往往会导致语法错误。TypeScript 在运行前需要先编译为 JavaScript，而在编译阶段就会进行类型检查，所以 **TypeScript 是静态类型**，这段 TypeScript 代码在编译阶段就会报错了：\n\n```ts\nlet foo = 1;\nfoo.split(' ');\n// Property 'split' does not exist on type 'number'.\n// 编译时会报错（数字没有 split 方法），无法通过编译\n```\n\n你可能会奇怪，这段 TypeScript 代码看上去和 JavaScript 没有什么区别呀。\n\n没错！大部分 JavaScript 代码都只需要经过少量的修改（或者完全不用修改）就变成 TypeScript 代码，这得益于 TypeScript 强大的[类型推论][]，即使不去手动声明变量 `foo` 的类型，也能在变量初始化时自动推论出它是一个 `number` 类型。\n\n完整的 TypeScript 代码是这样的：\n\n```ts\nlet foo: number = 1;\nfoo.split(' ');\n// Property 'split' does not exist on type 'number'.\n// 编译时会报错（数字没有 split 方法），无法通过编译\n```\n\n#### TypeScript 是弱类型\n\n类型系统按照「是否允许隐式类型转换」来分类，可以分为强类型和弱类型。\n\n以下这段代码不管是在 JavaScript 中还是在 TypeScript 中都是可以正常运行的，运行时数字 `1` 会被隐式类型转换为字符串 `'1'`，加号 `+` 被识别为字符串拼接，所以打印出结果是字符串 `'11'`。\n\n```js\nconsole.log(1 + '1');\n// 打印出字符串 '11'\n```\n\nTypeScript 是完全兼容 JavaScript 的，它不会修改 JavaScript 运行时的特性，所以**它们都是弱类型**。\n\n作为对比，Python 是强类型，以下代码会在运行时报错：\n\n```py\nprint(1 + '1')\n# TypeError: unsupported operand type(s) for +: 'int' and 'str'\n```\n\n若要修复该错误，需要进行强制类型转换：\n\n```py\nprint(str(1) + '1')\n# 打印出字符串 '11'\n```\n\n> 强/弱是相对的，Python 在处理整型和浮点型相加时，会将整型隐式转换为浮点型，但是这并不影响 Python 是强类型的结论，因为大部分情况下 Python 并不会进行隐式类型转换。相比而言，JavaScript 和 TypeScript 中不管加号两侧是什么类型，都可以通过隐式类型转换计算出一个结果——而不是报错——所以 JavaScript 和 TypeScript 都是弱类型。\n\n> 虽然 TypeScript 不限制加号两侧的类型，但是我们可以借助 TypeScript 提供的类型系统，以及 ESLint 提供的代码检查功能，来限制加号两侧必须同为数字或同为字符串<sup>[[5]](#link-5)</sup>。这在一定程度上使得 TypeScript 向「强类型」更近一步了——当然，这种限制是可选的。\n\n这样的类型系统体现了 TypeScript 的核心设计理念<sup>[[6]](#link-6)</sup>：在完整保留 JavaScript 运行时行为的基础上，通过引入静态类型系统来提高代码的可维护性，减少可能出现的 bug。\n\n### 适用于任何规模\n\nTypeScript 非常适用于大型项目——这是显而易见的，类型系统可以为大型项目带来更高的可维护性，以及更少的 bug。\n\n在中小型项目中推行 TypeScript 的最大障碍就是认为使用 TypeScript 需要写额外的代码，降低开发效率。但事实上，由于有[类型推论][]，大部分类型都不需要手动声明了。相反，TypeScript 增强了编辑器（IDE）的功能，包括代码补全、接口提示、跳转到定义、代码重构等，这在很大程度上提高了开发效率。而且 TypeScript 有近百个[编译选项][]，如果你认为类型检查过于严格，那么可以通过修改编译选项来降低类型检查的标准。\n\nTypeScript 还可以和 JavaScript 共存。这意味着如果你有一个使用 JavaScript 开发的旧项目，又想使用 TypeScript 的特性，那么你不需要急着把整个项目都迁移到 TypeScript，你可以使用 TypeScript 编写新文件，然后在后续更迭中逐步迁移旧文件。如果一些 JavaScript 文件的迁移成本太高，TypeScript 也提供了一个方案，可以让你在不修改 JavaScript 文件的前提下，编写一个[类型声明文件][]，实现旧项目的渐进式迁移。\n\n事实上，就算你从来没学习过 TypeScript，你也可能已经在不知不觉中使用到了 TypeScript——在 VSCode 编辑器中编写 JavaScript 时，代码补全和接口提示等功能就是通过 TypeScript Language Service 实现的<sup>[[7]](#link-7)</sup>：\n\n![what-is-typescript-vscode](../assets/what-is-typescript-vscode.png)\n\n一些第三方库原生支持了 TypeScript，在使用时就能获得代码补全了，比如 Vue 3.0<sup>[[8]](#link-8)</sup>：\n\n![what-is-typescript-vue](../assets/what-is-typescript-vue.png)\n\n有一些第三方库原生不支持 TypeScript，但是可以通过安装社区维护的类型声明库<sup>[[9]](#link-9)</sup>（比如通过运行 `npm install --save-dev @types/react` 来安装 React 的类型声明库）来获得代码补全能力——不管是在 JavaScript 项目中还是在 TypeScript 中项目中都是支持的：\n\n![what-is-typescript-react](../assets/what-is-typescript-react.png)\n\n由此可见，TypeScript 的发展已经深入到前端社区的方方面面了，任何规模的项目都或多或少得到了 TypeScript 的支持。\n\n### 与标准同步发展\n\nTypeScript 的另一个重要的特性就是坚持与 ECMAScript 标准<sup>[[10]](#link-10)</sup>同步发展。\n\nECMAScript 是 JavaScript 核心语法的标准，自 2015 年起，每年都会发布一个新版本，包含一些新的语法。\n\n一个新的语法从提案到变成正式标准，需要经历以下几个阶段：\n\n- Stage 0：展示阶段，仅仅是提出了讨论、想法，尚未正式提案。\n- Stage 1：征求意见阶段，提供抽象的 API 描述，讨论可行性，关键算法等。\n- Stage 2：草案阶段，使用正式的规范语言精确描述其语法和语义。\n- Stage 3：候选人阶段，语法的设计工作已完成，需要浏览器、Node.js 等环境支持，搜集用户的反馈。\n- Stage 4：定案阶段，已准备好将其添加到正式的 ECMAScript 标准中。\n\n一个语法进入到 Stage 3 阶段后，TypeScript 就会实现它。一方面，让我们可以尽早的使用到最新的语法，帮助它进入到下一个阶段；另一方面，处于 Stage 3 阶段的语法已经比较稳定了，基本不会有语法的变更，这使得我们能够放心的使用它。\n\n除了实现 ECMAScript 标准之外，TypeScript 团队也推进了诸多语法提案，比如可选链操作符（`?.`）<sup>[[11]](#link-11)</sup>、空值合并操作符（`??`）<sup>[[12]](#link-12)</sup>、Throw 表达式<sup>[[13]](#link-13)</sup>、正则匹配索引<sup>[[14]](#link-14)</sup>等。\n\n## 总结\n\n什么是 TypeScript？\n\n- TypeScript 是添加了类型系统的 JavaScript，适用于任何规模的项目。\n- TypeScript 是一门静态类型、弱类型的语言。\n- TypeScript 是完全兼容 JavaScript 的，它不会修改 JavaScript 运行时的特性。\n- TypeScript 可以编译为 JavaScript，然后运行在浏览器、Node.js 等任何能运行 JavaScript 的环境中。\n- TypeScript 拥有很多编译选项，类型检查的严格程度由你决定。\n- TypeScript 可以和 JavaScript 共存，这意味着 JavaScript 项目能够渐进式的迁移到 TypeScript。\n- TypeScript 增强了编辑器（IDE）的功能，提供了代码补全、接口提示、跳转到定义、代码重构等能力。\n- TypeScript 拥有活跃的社区，大多数常用的第三方库都提供了类型声明。\n- TypeScript 与标准同步发展，符合最新的 ECMAScript 标准（stage 3）。\n\n## 附：TypeScript 的发展历史\n\n- 2012-10：微软发布了 TypeScript 第一个版本（0.8），此前已经在微软内部开发了两年。\n- 2014-04：TypeScript 发布了 1.0 版本。\n- 2014-10：Angular 发布了 2.0 版本，它是一个基于 TypeScript 开发的前端框架。\n- 2015-01：ts-loader 发布，webpack 可以编译 TypeScript 文件了。\n- 2015-04：微软发布了 Visual Studio Code，它内置了对 TypeScript 语言的支持，它自身也是用 TypeScript 开发的。\n- 2016-05：`@types/react` 发布，TypeScript 可以开发 React 应用了。\n- 2016-05：`@types/node` 发布，TypeScript 可以开发 Node.js 应用了。\n- 2016-09：TypeScript 发布了 2.0 版本。\n- 2018-06：TypeScript 发布了 3.0 版本。\n- 2019-02：TypeScript 宣布由官方团队来维护 typescript-eslint，以支持在 TypeScript 文件中运行 ESLint 检查。\n- 2020-05：Deno 发布了 1.0 版本，它是一个 JavaScript 和 TypeScript 运行时。\n- 2020-08：TypeScript 发布了 4.0 版本。\n- 2020-09：Vue 发布了 3.0 版本，官方支持 TypeScript。\n\n## 参考资料\n\n1. <span id=\"link-1\">[TypeScript 官网](https://www.typescriptlang.org/)</span>\n2. <span id=\"link-2\">[第 2 章: 一等公民的函数](https://llh911001.gitbooks.io/mostly-adequate-guide-chinese/content/ch2.html) · 函数式编程指北</span>\n3. <span id=\"link-3\">[StackOverflow 2020 开发者调查报告](https://insights.stackoverflow.com/survey/2020)</span>\n4. <span id=\"link-4\">[斯坦福 JavaScript 第一课](https://web.stanford.edu/class/cs98si/slides/overview.html)</span>\n5. <span id=\"link-5\">[TypeScript ESLint 规则 `restrict-plus-operands`](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/restrict-plus-operands.md)</span>\n6. <span id=\"link-6\">[TypeScript 设计理念](https://github.com/microsoft/TypeScript/wiki/TypeScript-Design-Goals)</span>\n7. <span id=\"link-7\">[Visual Studio Code 中集成了 TypeScript](https://code.visualstudio.com/docs/languages/typescript)</span>\n8. <span id=\"link-8\">[Vue 3.0 支持 TypeScript](https://v3.vuejs.org/guide/typescript-support.html)</span>\n9. <span id=\"link-9\">[Definitely Typed](https://github.com/DefinitelyTyped/DefinitelyTyped)——TypeScript 团队帮助维护的类型定义仓库</span>\n10. <span id=\"link-10\">[ECMAScript 标准](https://tc39.es/process-document/)</span>\n11. <span id=\"link-11\">[可选链操作符（`?.`）](https://github.com/tc39/proposal-optional-chaining)</span>\n12. <span id=\"link-12\">[空值合并操作符（`??`）](https://github.com/tc39/proposal-nullish-coalescing)</span>\n13. <span id=\"link-13\">[Throw 表达式](https://github.com/tc39/proposal-throw-expressions)</span>\n14. <span id=\"link-14\">[正则匹配索引](https://github.com/tc39/proposal-regexp-match-indices)</span>\n"
  },
  {
    "path": "introduction/why-typescript.md",
    "content": "\n你可能或多或少听说过这样的言论：\n\n- TypeScript 只适用于大公司的大型项目\n- TypeScript 适合多人协作开发，一个人维护的项目就没必要使用了\n- TypeScript 学习成本高，开发成本高\n- 旧项目是 JavaScript，所以没必要改造成 TypeScript 了\n\n我想说，都 2021 年了，赶紧上车吧，绝大部分项目都应该使用 TypeScript！\n\n大型项目不必多说，TypeScript 的类型系统能够集成到 IDE（或编辑器）中，通过代码补全、代码提示、跳转到定义等功能，极大的提高代码的可维护性，提高开发效率，降低 bug 率。\n\n\n\n> 动态类型一时爽，代码重构火葬场。\n\n\n如果你没有学过 TypeScript，那么可能连这样的接口提示都看不懂了：\n\n![what-is-typescript-tip](../assets/what-is-typescript-tip.png)\n\n\n\n## 为什么选择 TypeScript\n\n[TypeScript 官网][TypeScript]列举了一些优势，不过我更愿意自己总结一下：\n\n### TypeScript 增加了代码的可读性和可维护性\n\n- 类型系统实际上是最好的文档，大部分的函数看看类型的定义就可以知道如何使用了\n- 可以在编译阶段就发现大部分错误，这总比在运行时候出错好\n- 增强了编辑器和 IDE 的功能，包括代码补全、接口提示、跳转到定义、代码重构等\n\n### TypeScript 非常包容\n\n- TypeScript 是 JavaScript 的超集，`.js` 文件可以直接重命名为 `.ts` 即可\n- 即使不显式的定义类型，也能够自动做出[类型推论](../basics/type-inference.md)\n- TypeScript 的类型系统是图灵完备的，可以定义从简单到复杂的几乎一切类型\n- 即使 TypeScript 编译报错，也可以生成 JavaScript 文件\n- 兼容第三方库，即使第三方库不是用 TypeScript 写的，也可以编写单独的类型文件供 TypeScript 读取\n\n### TypeScript 拥有活跃的社区\n\n- 大部分第三方库都有提供给 TypeScript 的类型定义文件\n- Angular、Vue、VS Code、Ant Design 等等耳熟能详的项目都是使用 TypeScript 编写的\n- TypeScript 拥抱了 ES6 规范，支持 ESNext 草案中处于第三阶状态（Stage 3）的特性\n\n### TypeScript 的缺点\n\n任何事物都是有两面性的，我认为 TypeScript 的弊端在于：\n\n- 有一定的学习成本，需要理解接口（Interfaces）、泛型（Generics）、类（Classes）、枚举类型（Enums）等前端工程师可能不是很熟悉的概念\n- 短期可能会增加一些开发成本，毕竟要多写一些类型的定义，不过对于一个需要长期维护的项目，TypeScript 能够减少其维护成本\n- 集成到构建流程需要一些工作量\n- 可能和一些库结合的不是很完美\n\n大家可以根据自己团队和项目的情况判断是否需要使用 TypeScript。\n\nStackOverflow 2020 开发者调查报告，TypeScript 击败 Python\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"typescript-tutorial\",\n  \"version\": \"0.1.0\",\n  \"description\": \"从 JavaScript 程序员的角度总结思考，循序渐进的理解 TypeScript\",\n  \"main\": \"README.md\",\n  \"scripts\": {\n    \"start\": \"deno run --unstable --allow-read --allow-write --allow-net --allow-run ../pagic/mod.ts build --serve --watch\",\n    \"build\": \"deno run --unstable --allow-read --allow-write --allow-net --allow-run ../pagic/mod.ts build\",\n    \"test\": \"npm run lint\",\n    \"pandoc\": \"pandoc -o TypeScript\\\\ 入门教程.epub --resource-path assets pandoc-metadata.txt $(cat pandoc-list.txt)\",\n    \"lint\": \"run-s eclint prettier lint-md eslint\",\n    \"lint:fix\": \"run-s eclint:fix prettier:fix lint-md:fix\",\n    \"eclint\": \"bash -c 'eclint check $(git ls-files -- . \\\":!:*.epub\\\")'\",\n    \"eclint:fix\": \"bash -c 'eclint fix $(git ls-files -- . \\\":!:*.epub\\\")'\",\n    \"prettier\": \"prettier -l \\\"./**/*\\\"\",\n    \"prettier:fix\": \"prettier --write -l \\\"./**/*\\\"\",\n    \"lint-md\": \"lint-md .\",\n    \"lint-md:fix\": \"lint-md --fix .\",\n    \"eslint\": \"eslint --ext .ts examples\",\n    \"eslint:fix\": \"eslint --ext .ts --fix examples\"\n  },\n  \"husky\": {\n    \"hooks\": {\n      \"pre-commit\": \"npm test\",\n      \"pre-push\": \"npm test\"\n    }\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/xcatliu/typescript-tutorial.git\"\n  },\n  \"keywords\": [\n    \"typescript\",\n    \"tutorial\",\n    \"javascript\"\n  ],\n  \"author\": \"xcatliu <xcatliu@gmail.com>\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/xcatliu/typescript-tutorial/issues\"\n  },\n  \"homepage\": \"https://github.com/xcatliu/typescript-tutorial#readme\",\n  \"devDependencies\": {\n    \"@typescript-eslint/eslint-plugin\": \"^5.59.9\",\n    \"@typescript-eslint/parser\": \"^5.59.9\",\n    \"eclint\": \"^2.8.1\",\n    \"eslint\": \"^8.42.0\",\n    \"eslint-config-alloy\": \"^5.0.0\",\n    \"eslint-plugin-react\": \"^7.32.2\",\n    \"husky\": \"^8.0.3\",\n    \"lint-md-cli\": \"^0.1.2\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"prettier\": \"^2.8.8\",\n    \"typescript\": \"^5.1.3\"\n  }\n}\n"
  },
  {
    "path": "pagic.config.tsx",
    "content": "import { React } from 'https://deno.land/x/pagic@v1.6.3/mod.ts';\n\nexport default {\n  srcDir: '.',\n  exclude: ['examples'],\n  theme: 'docs',\n  plugins: ['sidebar', 'prev_next', 'gitalk', 'ga'],\n  title: 'TypeScript 入门教程',\n  description: '从 JavaScript 程序员的角度总结思考，循序渐进的理解 TypeScript',\n  github: 'https://github.com/xcatliu/typescript-tutorial',\n  head: <link rel=\"icon\" type=\"image/png\" href=\"/favicon.png\" />,\n  nav: [\n    {\n      text: '赞助作者',\n      link: 'https://github.com/xcatliu/buy-me-a-coffee',\n      target: '_blank',\n      popover: (\n        <>\n          <img src=\"/assets/wechat.jpg\" width=\"256\" style={{ marginRight: '1rem', verticalAlign: 'top' }} />\n          <img src=\"/assets/alipay.jpg\" width=\"256\" style={{ verticalAlign: 'top' }} />\n        </>\n      ),\n    },\n    {\n      text: '加入微信群',\n      popover: (\n        <>\n          <p style={{ marginTop: 0, marginBottom: '1rem', width: 256 }}>一群已满，请扫码加二群</p>\n          <img src=\"/assets/join-wechat.jpg\" width=\"256\" />\n        </>\n      ),\n    },\n    {\n      text: '加入 QQ 群',\n      link: 'https://jq.qq.com/?_wv=1027&k=5nkkFCl',\n      target: '_blank',\n      popover: (\n        <>\n          <p style={{ marginTop: 0, marginBottom: '1rem', width: 256 }}>\n            一群（767142358）已满，请扫码加二群（706191218）\n          </p>\n          <img src=\"/assets/join-qq.jpg\" width=\"256\" />\n        </>\n      ),\n    },\n    {\n      text: '本网站使用 Pagic 构建',\n      link: 'https://github.com/xcatliu/pagic',\n      target: '_blank',\n    },\n  ],\n  sidebar: {\n    '/': [\n      {\n        link: 'introduction/README.md',\n        children: [\n          'introduction/what-is-typescript.md',\n          'introduction/get-typescript.md',\n          'introduction/hello-typescript.md',\n        ],\n      },\n      {\n        link: 'basics/README.md',\n        children: [\n          'basics/primitive-data-types.md',\n          'basics/any.md',\n          'basics/type-inference.md',\n          'basics/union-types.md',\n          'basics/type-of-object-interfaces.md',\n          'basics/type-of-array.md',\n          'basics/type-of-function.md',\n          'basics/type-assertion.md',\n          'basics/declaration-files.md',\n          'basics/built-in-objects.md',\n        ],\n      },\n      {\n        link: 'advanced/README.md',\n        children: [\n          'advanced/type-aliases.md',\n          'advanced/string-literal-types.md',\n          'advanced/tuple.md',\n          'advanced/enum.md',\n          'advanced/class.md',\n          'advanced/class-and-interfaces.md',\n          'advanced/generics.md',\n          'advanced/declaration-merging.md',\n          'advanced/decorator.md',\n          'advanced/further-reading.md',\n        ],\n      },\n      {\n        link: 'engineering/README.md',\n        children: ['engineering/lint.md', 'engineering/compiler-options.md'],\n      },\n      'thanks/README.md',\n    ],\n  },\n  tools: {\n    editOnGitHub: true,\n    backToTop: true,\n  },\n  //   tocAd: (\n  //     <div\n  //       dangerouslySetInnerHTML={{\n  //         __html: `\n  // <script async src=\"https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js\"></script>\n  // <!-- 192*128 -->\n  // <ins\n  //   class=\"adsbygoogle\"\n  //   style=\"display:inline-block;width:192px;height:128px\"\n  //   data-ad-client=\"ca-pub-8483371329009107\"\n  //   data-ad-slot=\"6487368873\"\n  // ></ins>\n  // <script>\n  //   (adsbygoogle = window.adsbygoogle || []).push({});\n  // </script>`\n  //       }}\n  //     />\n  //   ),\n  gitalk: {\n    clientID: '29aa4941759fc887ed4f',\n    clientSecret: '33e355efdf3a1959624506a5d88311145208471b',\n    repo: 'typescript-tutorial',\n    owner: 'xcatliu',\n    admin: ['xcatliu'],\n    pagerDirection: 'first',\n  },\n  ga: {\n    id: 'UA-45256157-14',\n  },\n  port: 8001,\n};\n"
  },
  {
    "path": "pandoc-list.txt",
    "content": "README.md\nintroduction/README.md\nintroduction/what-is-typescript.md\nintroduction/get-typescript.md\nintroduction/hello-typescript.md\nbasics/README.md\nbasics/primitive-data-types.md\nbasics/any.md\nbasics/type-inference.md\nbasics/union-types.md\nbasics/type-of-object-interfaces.md\nbasics/type-of-array.md\nbasics/type-of-function.md\nbasics/type-assertion.md\nbasics/declaration-files.md\nbasics/built-in-objects.md\nadvanced/README.md\nadvanced/type-aliases.md\nadvanced/string-literal-types.md\nadvanced/tuple.md\nadvanced/enum.md\nadvanced/class.md\nadvanced/class-and-interfaces.md\nadvanced/generics.md\nadvanced/declaration-merging.md\nadvanced/further-reading.md\nengineering/README.md\nengineering/lint.md\nengineering/compiler-options.md\nthanks/README.md\n"
  },
  {
    "path": "pandoc-metadata.txt",
    "content": "---\ntitle: TypeScript 入门教程\nauthor: xcatliu\ndescription: 从 JavaScript 程序员的角度总结思考，循序渐进的理解 TypeScript\nlanguage: zh-CN\ncover-image: pandoc-cover.jpg\n...\n"
  },
  {
    "path": "thanks/README.md",
    "content": "# 感谢\n\n- 感谢[创造和维护 TypeScript 的人们](https://github.com/Microsoft/TypeScript/graphs/contributors)，给我们带来了如此优秀的工具\n- 感谢 [@zhongsp](https://github.com/zhongsp/) 对[官方手册的翻译](https://zhongsp.gitbooks.io/typescript-handbook/content/index.html)，本书参考了大量他的翻译，能一直坚持跟进非常不容易\n- 感谢 [@阮一峰](http://www.ruanyifeng.com/home.html) 老师的 [ECMAScript 6 入门](http://es6.ruanyifeng.com/)，本书引用了多处 ES6 的知识\n\n最后，感谢你阅读完本书，希望你会有所收获。\n\n## 下一步\n\n- 在 [GitHub](https://github.com/xcatliu/typescript-tutorial) 上关注本书\n- 阅读[官方手册](http://www.typescriptlang.org/docs/handbook/basic-types.html)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/)）巩固知识\n- 阅读 [Project Configuration](http://www.typescriptlang.org/docs/handbook/tsconfig-json.html)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/tsconfig.json.html)） 学习如何配置 TypeScript 工程\n- 查看[官方示例](http://www.typescriptlang.org/samples/index.html)，学习真实项目\n"
  }
]