Repository: xcatliu/typescript-tutorial Branch: master Commit: 1b71bebef402 Files: 161 Total size: 147.8 KB Directory structure: gitextract_f_xb4944/ ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github/ │ ├── FUNDING.yml │ └── workflows/ │ └── gh-pages.yml ├── .gitignore ├── .lintmdrc ├── .npmrc ├── .prettierignore ├── .prettierrc.js ├── .vscode/ │ └── settings.json ├── README.md ├── TypeScript 入门教程 2020.epub ├── TypeScript 入门教程.epub ├── advanced/ │ ├── README.md │ ├── class-and-interfaces.md │ ├── class.md │ ├── declaration-merging.md │ ├── decorator.md │ ├── enum.md │ ├── further-reading.md │ ├── generics.md │ ├── string-literal-types.md │ ├── tuple.md │ └── type-aliases.md ├── basics/ │ ├── README.md │ ├── any.md │ ├── built-in-objects.md │ ├── declaration-files.md │ ├── primitive-data-types.md │ ├── type-assertion.md │ ├── type-inference.md │ ├── type-of-array.md │ ├── type-of-function.md │ ├── type-of-object-interfaces.md │ └── union-types.md ├── engineering/ │ ├── README.md │ ├── compiler-options.md │ └── lint.md ├── examples/ │ ├── compiler-options/ │ │ ├── 01-allowJs/ │ │ │ ├── false/ │ │ │ │ ├── lib/ │ │ │ │ │ └── index.js │ │ │ │ ├── package.json │ │ │ │ ├── src/ │ │ │ │ │ ├── foo.js │ │ │ │ │ └── index.ts │ │ │ │ └── tsconfig.json │ │ │ └── true/ │ │ │ ├── lib/ │ │ │ │ ├── foo.js │ │ │ │ └── index.js │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ ├── foo.js │ │ │ │ └── index.ts │ │ │ └── tsconfig.json │ │ └── 02-allowSyntheticDefaultImports/ │ │ ├── false/ │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ └── index.ts │ │ │ └── tsconfig.json │ │ └── true/ │ │ ├── package.json │ │ ├── src/ │ │ │ └── index.ts │ │ └── tsconfig.json │ └── declaration-files/ │ ├── 01-jquery/ │ │ ├── src/ │ │ │ └── index.ts │ │ └── tsconfig.json │ ├── 02-declare-var/ │ │ ├── src/ │ │ │ └── index.ts │ │ └── tsconfig.json │ ├── 03-jquery-d-ts/ │ │ ├── src/ │ │ │ ├── index.ts │ │ │ └── jQuery.d.ts │ │ └── tsconfig.json │ ├── 04-declare-const-jquery/ │ │ ├── src/ │ │ │ ├── index.ts │ │ │ └── jQuery.d.ts │ │ └── tsconfig.json │ ├── 05-declare-jquery-value/ │ │ ├── src/ │ │ │ └── jQuery.d.ts │ │ └── tsconfig.json │ ├── 06-declare-function/ │ │ ├── src/ │ │ │ ├── index.ts │ │ │ └── jQuery.d.ts │ │ └── tsconfig.json │ ├── 07-declare-class/ │ │ ├── src/ │ │ │ ├── Animal.d.ts │ │ │ └── index.ts │ │ └── tsconfig.json │ ├── 08-declare-enum/ │ │ ├── src/ │ │ │ ├── Directions.d.ts │ │ │ └── index.ts │ │ └── tsconfig.json │ ├── 09-declare-namespace/ │ │ ├── src/ │ │ │ ├── index.ts │ │ │ └── jQuery.d.ts │ │ └── tsconfig.json │ ├── 10-declare-namespace-nesting/ │ │ ├── src/ │ │ │ ├── index.ts │ │ │ └── jQuery.d.ts │ │ └── tsconfig.json │ ├── 11-declare-namespace-dot/ │ │ ├── src/ │ │ │ ├── index.ts │ │ │ └── jQuery.d.ts │ │ └── tsconfig.json │ ├── 12-interface/ │ │ ├── src/ │ │ │ ├── index.ts │ │ │ └── jQuery.d.ts │ │ └── tsconfig.json │ ├── 13-avoid-name-conflict/ │ │ ├── src/ │ │ │ ├── index.ts │ │ │ └── jQuery.d.ts │ │ └── tsconfig.json │ ├── 14-declaration-merging/ │ │ ├── src/ │ │ │ ├── index.ts │ │ │ └── jQuery.d.ts │ │ └── tsconfig.json │ ├── 15-export/ │ │ ├── src/ │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── types/ │ │ └── foo/ │ │ └── index.d.ts │ ├── 16-declare-and-export/ │ │ ├── src/ │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── types/ │ │ └── foo/ │ │ └── index.d.ts │ ├── 17-export-namespace/ │ │ ├── src/ │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── types/ │ │ └── foo/ │ │ └── index.d.ts │ ├── 18-export-default/ │ │ ├── src/ │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── types/ │ │ └── foo/ │ │ └── index.d.ts │ ├── 19-export-default-enum-error/ │ │ ├── src/ │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── types/ │ │ └── foo/ │ │ └── index.d.ts │ ├── 20-export-default-enum/ │ │ ├── src/ │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── types/ │ │ └── foo/ │ │ └── index.d.ts │ ├── 21-export-equal/ │ │ ├── src/ │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── types/ │ │ └── foo/ │ │ └── index.d.ts │ ├── 22-export-as-namespace/ │ │ ├── src/ │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── types/ │ │ └── foo/ │ │ └── index.d.ts │ ├── 23-merge-global-interface/ │ │ ├── src/ │ │ │ └── index.ts │ │ └── tsconfig.json │ ├── 24-merge-global-namespace/ │ │ ├── package.json │ │ ├── src/ │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── types/ │ │ └── jquery-plugin/ │ │ └── index.d.ts │ ├── 25-declare-global/ │ │ ├── src/ │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── types/ │ │ └── foo/ │ │ └── index.d.ts │ ├── 26-declare-module/ │ │ ├── package.json │ │ ├── src/ │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── types/ │ │ └── moment-plugin/ │ │ └── index.d.ts │ ├── 27-multiple-declare-module/ │ │ ├── src/ │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── types/ │ │ └── foo-bar.d.ts │ ├── 28-triple-slash-directives/ │ │ ├── package.json │ │ ├── src/ │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── types/ │ │ └── jquery-plugin/ │ │ └── index.d.ts │ ├── 29-triple-slash-directives-global/ │ │ ├── package.json │ │ ├── src/ │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── types/ │ │ └── node-plugin/ │ │ └── index.d.ts │ └── 30-auto-d-ts/ │ ├── lib/ │ │ ├── bar/ │ │ │ ├── index.d.ts │ │ │ └── index.js │ │ ├── index.d.ts │ │ └── index.js │ ├── package.json │ ├── src/ │ │ ├── bar/ │ │ │ └── index.ts │ │ └── index.ts │ └── tsconfig.json ├── introduction/ │ ├── README.md │ ├── get-typescript.md │ ├── hello-typescript.md │ ├── what-is-typescript.md │ └── why-typescript.md ├── package.json ├── pagic.config.tsx ├── pandoc-list.txt ├── pandoc-metadata.txt └── thanks/ └── README.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ # EditorConfig is awesome: https://EditorConfig.org # top-most EditorConfig file root = true # Unix-style newlines with a newline ending every file [*] end_of_line = lf insert_final_newline = true # Matches multiple files with brace expansion notation # Set default charset [*] charset = utf-8 # 4 space indentation # [*] # indent_style = space # indent_size = 4 ================================================ FILE: .eslintignore ================================================ *.d.ts ================================================ FILE: .eslintrc.js ================================================ module.exports = { extends: ['alloy', 'alloy/react', 'alloy/typescript'], env: { // Your environments (which contains several predefined global variables) // // browser: true, // mocha: true, // jquery: true }, globals: { // Your global variables (setting to false means it's not allowed to be reassigned) // // myGlobal: false }, rules: { // Customize your rules 'no-undef': 'off', 'prefer-arrow-callback': 'off', '@typescript-eslint/no-invalid-this': 'off', '@typescript-eslint/no-require-imports': 'off', '@typescript-eslint/method-signature-style': 'off', }, }; ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: xcatliu patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username custom: https://github.com/xcatliu/buy-me-a-coffee ================================================ FILE: .github/workflows/gh-pages.yml ================================================ name: gh-pages on: push: branches: - master jobs: build-and-deploy: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2 with: fetch-depth: 0 - name: Setup deno uses: denolib/setup-deno@v2 with: deno-version: v1.34.1 - name: Build gh-pages run: | curl -fsSL https://deno.land/x/install/install.sh | sh export DENO_INSTALL="/home/runner/.deno" export PATH="$DENO_INSTALL/bin:$PATH" deno --version deno install --unstable --allow-read --allow-write --allow-net --allow-run -n pagic https://deno.land/x/pagic@v1.6.3/mod.ts pagic build - name: Deploy gh-pages uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./dist cname: ts.xcatliu.com ================================================ FILE: .gitignore ================================================ .DS_Store node_modules dist ================================================ FILE: .lintmdrc ================================================ { "excludeFiles": [], "rules": { "no-trailing-punctuation": 0, "no-long-code": 0 } } ================================================ FILE: .npmrc ================================================ registry=https://registry.npmjs.org/ ================================================ FILE: .prettierignore ================================================ *.md *.png *.epub node_modules assets .DS_Store .editorconfig .eslintignore .gitignore .lintmdrc .prettierignore .npmrc pnpm-lock.yaml examples dist ================================================ FILE: .prettierrc.js ================================================ // .prettierrc.js module.exports = { // 一行最多 120 字符 printWidth: 120, // 使用 2 个空格缩进 tabWidth: 2, // 不使用缩进符,而使用空格 useTabs: false, // 行尾需要有分号 semi: true, // 使用单引号 singleQuote: true, // 对象的 key 仅在必要时用引号 quoteProps: 'as-needed', // jsx 不使用单引号,而使用双引号 jsxSingleQuote: false, // 末尾需要有逗号 trailingComma: 'all', // 大括号内的首尾需要空格 bracketSpacing: true, // jsx 标签的反尖括号需要换行 bracketSameLine: false, // 箭头函数,只有一个参数的时候,也需要括号 arrowParens: 'always', // 每个文件格式化的范围是文件的全部内容 rangeStart: 0, rangeEnd: Infinity, // 不需要写文件开头的 @prettier requirePragma: false, // 不需要自动在文件开头插入 @prettier insertPragma: false, // 使用默认的折行标准 proseWrap: 'preserve', // 根据显示样式决定 html 要不要折行 htmlWhitespaceSensitivity: 'css', // vue 文件中的 script 和 style 内不用缩进 vueIndentScriptAndStyle: false, // 换行符使用 lf endOfLine: 'lf', // 格式化嵌入的内容 embeddedLanguageFormatting: 'auto', // html, vue, jsx 中每个属性占一行 singleAttributePerLine: false, }; ================================================ FILE: .vscode/settings.json ================================================ { "files.eol": "\n", "editor.tabSize": 2, "editor.formatOnSave": true, "editor.defaultFormatter": "esbenp.prettier-vscode", "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"], "editor.codeActionsOnSave": { // 保存时自动修复 ESLint 错误 "source.fixAll.eslint": true }, "typescript.tsdk": "node_modules/typescript/lib", "[json]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[jsonc]": { "editor.defaultFormatter": "esbenp.prettier-vscode" } } ================================================ FILE: README.md ================================================ --- next: introduction/README.md --- # TypeScript 入门教程 [![Actions Status](https://github.com/xcatliu/typescript-tutorial/workflows/gh-pages/badge.svg)](https://github.com/xcatliu/typescript-tutorial/actions) 从 JavaScript 程序员的角度总结思考,循序渐进的理解 TypeScript。 ## 关于本书 - [在线阅读](https://ts.xcatliu.com/) - [GitHub 地址][GitHub] - 作者:[xcatliu](https://github.com/xcatliu/) - 本网站使用 [Pagic](https://github.com/xcatliu/pagic) 构建 本书是作者在学习 [TypeScript] 后整理的学习笔记。 随着对 TypeScript 理解的加深和 TypeScript 社区的发展,本书也会做出相应的更新,欢迎大家 [Star 收藏][GitHub]。 - 发现文章内容有问题,可以直接在页面下方评论 - 对项目的建议,可以[提交 issue](https://github.com/xcatliu/typescript-tutorial/issues/new) 向作者反馈 - 欢迎直接提交 pull-request 参与贡献 ## 为什么要写本书 TypeScript 虽然有[官方手册][Handbook]及其[非官方中文版][中文手册],但是它每一章都希望能详尽的描述一个概念,导致前面的章节就会包含很多后面才会学习到的内容,而有些本该一开始就了解的基础知识却在后面才会涉及。如果是初学者,可能需要阅读多次才能理解。所以它更适合用来查阅,而不是学习。 与官方手册不同,本书着重于从 JavaScript 程序员的角度总结思考,循序渐进的理解 TypeScript,希望能给大家一些帮助和启示。 由于一些知识点与官方手册重合度很高,本书会在相应章节推荐直接阅读中文手册。 ## 关于 TypeScript [TypeScript] 是 JavaScript 的一个超集,主要提供了**类型系统**和**对 ES6 的支持**,它由 Microsoft 开发,代码[开源于 GitHub](https://github.com/Microsoft/TypeScript) 上。 它的第一个版本发布于 2012 年 10 月,经历了多次更新后,现在已成为前端社区中不可忽视的力量,不仅在 Microsoft 内部得到广泛运用,而且 Google 开发的 [Angular](https://angular.io/) 从 2.0 开始就使用了 TypeScript 作为开发语言,[Vue](https://vuejs.org/) 3.0 也使用 TypeScript 进行了重构。 ## 适合人群 本书适合以下人群 - 熟悉 JavaScript,至少阅读过一遍[《JavaScript 高级程序设计》](https://book.douban.com/subject/10546125/) - 了解 ES6,推荐阅读 [ECMAScript 6 入门] - 了解 Node.js,会用 npm 安装及使用一些工具 - 想了解 TypeScript 或者想对 TypeScript 有更深的理解 本书**不适合**以下人群 - 没有系统学习过 JavaScript - 已经能够很熟练的运用 TypeScript ## 评价 > 《TypeScript 入门教程》全面介绍了 TypeScript 强大的类型系统,完整而简洁,示例丰富,比官方文档更易读,非常适合作为初学者学习 TypeScript 的第一本书。 > > —— [阮一峰](https://github.com/ruanyf) ## 版权许可 本书采用「保持署名—非商用」创意共享 4.0 许可证。 只要保持原作者署名和非商用,您可以自由地阅读、分享、修改本书。 详细的法律条文请参见[创意共享](http://creativecommons.org/licenses/by-nc/4.0/)网站。 ## 相关资料 - [TypeScript 官网][TypeScript] - [Handbook]([中文版][中文手册]) - [ECMAScript 6 入门] [GitHub]: https://github.com/xcatliu/typescript-tutorial [TypeScript]: http://www.typescriptlang.org/ [Handbook]: http://www.typescriptlang.org/docs/handbook/basic-types.html [中文手册]: https://zhongsp.gitbook.io/typescript-handbook/ [ECMAScript 6 入门]: http://es6.ruanyifeng.com/ ================================================ FILE: advanced/README.md ================================================ # 进阶 本部分介绍一些高级的类型与技术,具体内容包括: - [类型别名](type-aliases.md) - [字符串字面量类型](string-literal-types.md) - [元组](tuple.md) - [枚举](enum.md) - [类](class.md) - [类与接口](class-and-interfaces.md) - [泛型](generics.md) - [声明合并](declaration-merging.md) - [扩展阅读](further-reading.md) ================================================ FILE: advanced/class-and-interfaces.md ================================================ # 类与接口 [之前学习过](../basics/type-of-object-interfaces.md),接口(Interfaces)可以用于对「对象的形状(Shape)」进行描述。 这一章主要介绍接口的另一个用途,对类的一部分行为进行抽象。 ## 类实现接口 实现(implements)是面向对象中的一个重要概念。一般来讲,一个类只能继承自另一个类,有时候不同类之间可以有一些共有的特性,这时候就可以把特性提取成接口(interfaces),用 `implements` 关键字来实现。这个特性大大提高了面向对象的灵活性。 举例来说,门是一个类,防盗门是门的子类。如果防盗门有一个报警器的功能,我们可以简单的给防盗门添加一个报警方法。这时候如果有另一个类,车,也有报警器的功能,就可以考虑把报警器提取出来,作为一个接口,防盗门和车都去实现它: ```ts interface Alarm { alert(): void; } class Door { } class SecurityDoor extends Door implements Alarm { alert() { console.log('SecurityDoor alert'); } } class Car implements Alarm { alert() { console.log('Car alert'); } } ``` 一个类可以实现多个接口: ```ts interface Alarm { alert(): void; } interface Light { lightOn(): void; lightOff(): void; } class Car implements Alarm, Light { alert() { console.log('Car alert'); } lightOn() { console.log('Car light on'); } lightOff() { console.log('Car light off'); } } ``` 上例中,`Car` 实现了 `Alarm` 和 `Light` 接口,既能报警,也能开关车灯。 ## 接口继承接口 接口与接口之间可以是继承关系: ```ts interface Alarm { alert(): void; } interface LightableAlarm extends Alarm { lightOn(): void; lightOff(): void; } ``` 这很好理解,`LightableAlarm` 继承了 `Alarm`,除了拥有 `alert` 方法之外,还拥有两个新方法 `lightOn` 和 `lightOff`。 ## 接口继承类 常见的面向对象语言中,接口是不能继承类的,但是在 TypeScript 中却是可以的: ```ts class Point { x: number; y: number; constructor(x: number, y: number) { this.x = x; this.y = y; } } interface Point3d extends Point { z: number; } let point3d: Point3d = {x: 1, y: 2, z: 3}; ``` 为什么 TypeScript 会支持接口继承类呢? 实际上,当我们在声明 `class Point` 时,除了会创建一个名为 `Point` 的类之外,同时也创建了一个名为 `Point` 的类型(实例的类型)。 所以我们既可以将 `Point` 当做一个类来用(使用 `new Point` 创建它的实例): ```ts class Point { x: number; y: number; constructor(x: number, y: number) { this.x = x; this.y = y; } } const p = new Point(1, 2); ``` 也可以将 `Point` 当做一个类型来用(使用 `: Point` 表示参数的类型): ```ts class Point { x: number; y: number; constructor(x: number, y: number) { this.x = x; this.y = y; } } function printPoint(p: Point) { console.log(p.x, p.y); } printPoint(new Point(1, 2)); ``` 这个例子实际上可以等价于: ```ts class Point { x: number; y: number; constructor(x: number, y: number) { this.x = x; this.y = y; } } interface PointInstanceType { x: number; y: number; } function printPoint(p: PointInstanceType) { console.log(p.x, p.y); } printPoint(new Point(1, 2)); ``` 上例中我们新声明的 `PointInstanceType` 类型,与声明 `class Point` 时创建的 `Point` 类型是等价的。 所以回到 `Point3d` 的例子中,我们就能很容易的理解为什么 TypeScript 会支持接口继承类了: ```ts class Point { x: number; y: number; constructor(x: number, y: number) { this.x = x; this.y = y; } } interface PointInstanceType { x: number; y: number; } // 等价于 interface Point3d extends PointInstanceType interface Point3d extends Point { z: number; } let point3d: Point3d = {x: 1, y: 2, z: 3}; ``` 当我们声明 `interface Point3d extends Point` 时,`Point3d` 继承的实际上是类 `Point` 的实例的类型。 换句话说,可以理解为定义了一个接口 `Point3d` 继承另一个接口 `PointInstanceType`。 所以「接口继承类」和「接口继承接口」没有什么本质的区别。 值得注意的是,`PointInstanceType` 相比于 `Point`,缺少了 `constructor` 方法,这是因为声明 `Point` 类时创建的 `Point` 类型是不包含构造函数的。另外,除了构造函数是不包含的,静态属性或静态方法也是不包含的(实例的类型当然不应该包括构造函数、静态属性或静态方法)。 换句话说,声明 `Point` 类时创建的 `Point` 类型只包含其中的实例属性和实例方法: ```ts class Point { /** 静态属性,坐标系原点 */ static origin = new Point(0, 0); /** 静态方法,计算与原点距离 */ static distanceToOrigin(p: Point) { return Math.sqrt(p.x * p.x + p.y * p.y); } /** 实例属性,x 轴的值 */ x: number; /** 实例属性,y 轴的值 */ y: number; /** 构造函数 */ constructor(x: number, y: number) { this.x = x; this.y = y; } /** 实例方法,打印此点 */ printPoint() { console.log(this.x, this.y); } } interface PointInstanceType { x: number; y: number; printPoint(): void; } let p1: Point; let p2: PointInstanceType; ``` 上例中最后的类型 `Point` 和类型 `PointInstanceType` 是等价的。 同样的,在接口继承类的时候,也只会继承它的实例属性和实例方法。 ## 参考 - [Interfaces](http://www.typescriptlang.org/docs/handbook/interfaces.html)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Interfaces.html)) ================================================ FILE: advanced/class.md ================================================ # 类 传统方法中,JavaScript 通过构造函数实现类的概念,通过原型链实现继承。而在 ES6 中,我们终于迎来了 `class`。 TypeScript 除了实现了所有 ES6 中的类的功能以外,还添加了一些新的用法。 这一节主要介绍类的用法,下一节再介绍如何定义类的类型。 ## 类的概念 虽然 JavaScript 中有类的概念,但是可能大多数 JavaScript 程序员并不是非常熟悉类,这里对类相关的概念做一个简单的介绍。 - 类(Class):定义了一件事物的抽象特点,包含它的属性和方法 - 对象(Object):类的实例,通过 `new` 生成 - 面向对象(OOP)的三大特性:封装、继承、多态 - 封装(Encapsulation):将对数据的操作细节隐藏起来,只暴露对外的接口。外界调用端不需要(也不可能)知道细节,就能通过对外提供的接口来访问该对象,同时也保证了外界无法任意更改对象内部的数据 - 继承(Inheritance):子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性 - 多态(Polymorphism):由继承而产生了相关的不同的类,对同一个方法可以有不同的响应。比如 `Cat` 和 `Dog` 都继承自 `Animal`,但是分别实现了自己的 `eat` 方法。此时针对某一个实例,我们无需了解它是 `Cat` 还是 `Dog`,就可以直接调用 `eat` 方法,程序会自动判断出来应该如何执行 `eat` - 存取器(getter & setter):用以改变属性的读取和赋值行为 - 修饰符(Modifiers):修饰符是一些关键字,用于限定成员或类型的性质。比如 `public` 表示公有属性或方法 - 抽象类(Abstract Class):抽象类是供其他类继承的基类,抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现 - 接口(Interfaces):不同类之间公有的属性或方法,可以抽象成一个接口。接口可以被类实现(implements)。一个类只能继承自另一个类,但是可以实现多个接口 ## ES6 中类的用法 下面我们先回顾一下 ES6 中类的用法,更详细的介绍可以参考 [ECMAScript 6 入门 - Class]。 ### 属性和方法 使用 `class` 定义类,使用 `constructor` 定义构造函数。 通过 `new` 生成新实例的时候,会自动调用构造函数。 ```js class Animal { name; constructor(name) { this.name = name; } sayHi() { return `My name is ${this.name}`; } } let a = new Animal('Jack'); console.log(a.sayHi()); // My name is Jack ``` ### 类的继承 使用 `extends` 关键字实现继承,子类中使用 `super` 关键字来调用父类的构造函数和方法。 ```js class Cat extends Animal { constructor(name) { super(name); // 调用父类的 constructor(name) console.log(this.name); } sayHi() { return 'Meow, ' + super.sayHi(); // 调用父类的 sayHi() } } let c = new Cat('Tom'); // Tom console.log(c.sayHi()); // Meow, My name is Tom ``` ### 存取器 使用 getter 和 setter 可以改变属性的赋值和读取行为: ```js class Animal { constructor(name) { this.name = name; } get name() { return 'Jack'; } set name(value) { console.log('setter: ' + value); } } let a = new Animal('Kitty'); // setter: Kitty a.name = 'Tom'; // setter: Tom console.log(a.name); // Jack ``` ### 静态方法 使用 `static` 修饰符修饰的方法称为静态方法,它们不需要实例化,而是直接通过类来调用: ```js class Animal { static isAnimal(a) { return a instanceof Animal; } } let a = new Animal('Jack'); Animal.isAnimal(a); // true a.isAnimal(a); // TypeError: a.isAnimal is not a function ``` ## ES7 中类的用法 ES7 中有一些关于类的提案,TypeScript 也实现了它们,这里做一个简单的介绍。 ### 实例属性 ES6 中实例的属性只能通过构造函数中的 `this.xxx` 来定义,ES7 提案中可以直接在类里面定义: ```js class Animal { name = 'Jack'; constructor() { // ... } } let a = new Animal(); console.log(a.name); // Jack ``` ### 静态属性 ES7 提案中,可以使用 `static` 定义一个静态属性: ```js class Animal { static num = 42; constructor() { // ... } } console.log(Animal.num); // 42 ``` ## TypeScript 中类的用法 ### public private 和 protected TypeScript 可以使用三种访问修饰符(Access Modifiers),分别是 `public`、`private` 和 `protected`。 - `public` 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 `public` 的 - `private` 修饰的属性或方法是私有的,不能在声明它的类的外部访问 - `protected` 修饰的属性或方法是受保护的,它和 `private` 类似,区别是它在子类中也是允许被访问的 下面举一些例子: ```ts class Animal { public name; public constructor(name) { this.name = name; } } let a = new Animal('Jack'); console.log(a.name); // Jack a.name = 'Tom'; console.log(a.name); // Tom ``` 上面的例子中,`name` 被设置为了 `public`,所以直接访问实例的 `name` 属性是允许的。 很多时候,我们希望有的属性是无法直接存取的,这时候就可以用 `private` 了: ```ts class Animal { private name; public constructor(name) { this.name = name; } } let a = new Animal('Jack'); console.log(a.name); a.name = 'Tom'; // index.ts(9,13): error TS2341: Property 'name' is private and only accessible within class 'Animal'. // index.ts(10,1): error TS2341: Property 'name' is private and only accessible within class 'Animal'. ``` 需要注意的是,TypeScript 编译之后的代码中,并没有限制 `private` 属性在外部的可访问性。 上面的例子编译后的代码是: ```js var Animal = (function () { function Animal(name) { this.name = name; } return Animal; })(); var a = new Animal('Jack'); console.log(a.name); a.name = 'Tom'; ``` 使用 `private` 修饰的属性或方法,在子类中也是不允许访问的: ```ts class Animal { private name; public constructor(name) { this.name = name; } } class Cat extends Animal { constructor(name) { super(name); console.log(this.name); } } // index.ts(11,17): error TS2341: Property 'name' is private and only accessible within class 'Animal'. ``` 而如果是用 `protected` 修饰,则允许在子类中访问: ```ts class Animal { protected name; public constructor(name) { this.name = name; } } class Cat extends Animal { constructor(name) { super(name); console.log(this.name); } } ``` 当构造函数修饰为 `private` 时,该类不允许被继承或者实例化: ```ts class Animal { public name; private constructor(name) { this.name = name; } } class Cat extends Animal { constructor(name) { super(name); } } let a = new Animal('Jack'); // index.ts(7,19): TS2675: Cannot extend a class 'Animal'. Class constructor is marked as private. // index.ts(13,9): TS2673: Constructor of class 'Animal' is private and only accessible within the class declaration. ``` 当构造函数修饰为 `protected` 时,该类只允许被继承: ```ts class Animal { public name; protected constructor(name) { this.name = name; } } class Cat extends Animal { constructor(name) { super(name); } } let a = new Animal('Jack'); // index.ts(13,9): TS2674: Constructor of class 'Animal' is protected and only accessible within the class declaration. ``` ### 参数属性 修饰符和`readonly`还可以使用在构造函数参数中,等同于类中定义该属性同时给该属性赋值,使代码更简洁。 ```ts class Animal { // public name: string; public constructor(public name) { // this.name = name; } } ``` ### readonly 只读属性关键字,只允许出现在属性声明或索引签名或构造函数中。 ```ts class Animal { readonly name; public constructor(name) { this.name = name; } } let a = new Animal('Jack'); console.log(a.name); // Jack a.name = 'Tom'; // index.ts(10,3): TS2540: Cannot assign to 'name' because it is a read-only property. ``` 注意如果 `readonly` 和其他访问修饰符同时存在的话,需要写在其后面。 ```ts class Animal { // public readonly name; public constructor(public readonly name) { // this.name = name; } } ``` ### 抽象类 `abstract` 用于定义抽象类和其中的抽象方法。 什么是抽象类? 首先,抽象类是不允许被实例化的: ```ts abstract class Animal { public name; public constructor(name) { this.name = name; } public abstract sayHi(); } let a = new Animal('Jack'); // index.ts(9,11): error TS2511: Cannot create an instance of the abstract class 'Animal'. ``` 上面的例子中,我们定义了一个抽象类 `Animal`,并且定义了一个抽象方法 `sayHi`。在实例化抽象类的时候报错了。 其次,抽象类中的抽象方法必须被子类实现: ```ts abstract class Animal { public name; public constructor(name) { this.name = name; } public abstract sayHi(); } class Cat extends Animal { public eat() { console.log(`${this.name} is eating.`); } } let cat = new Cat('Tom'); // index.ts(9,7): error TS2515: Non-abstract class 'Cat' does not implement inherited abstract member 'sayHi' from class 'Animal'. ``` 上面的例子中,我们定义了一个类 `Cat` 继承了抽象类 `Animal`,但是没有实现抽象方法 `sayHi`,所以编译报错了。 下面是一个正确使用抽象类的例子: ```ts abstract class Animal { public name; public constructor(name) { this.name = name; } public abstract sayHi(); } class Cat extends Animal { public sayHi() { console.log(`Meow, My name is ${this.name}`); } } let cat = new Cat('Tom'); ``` 上面的例子中,我们实现了抽象方法 `sayHi`,编译通过了。 需要注意的是,即使是抽象方法,TypeScript 的编译结果中,仍然会存在这个类,上面的代码的编译结果是: ```js var __extends = (this && this.__extends) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : ((__.prototype = b.prototype), new __()); }; var Animal = (function () { function Animal(name) { this.name = name; } return Animal; })(); var Cat = (function (_super) { __extends(Cat, _super); function Cat() { _super.apply(this, arguments); } Cat.prototype.sayHi = function () { console.log('Meow, My name is ' + this.name); }; return Cat; })(Animal); var cat = new Cat('Tom'); ``` ## 类的类型 给类加上 TypeScript 的类型很简单,与接口类似: ```ts class Animal { name: string; constructor(name: string) { this.name = name; } sayHi(): string { return `My name is ${this.name}`; } } let a: Animal = new Animal('Jack'); console.log(a.sayHi()); // My name is Jack ``` ## 参考 - [Classes](http://www.typescriptlang.org/docs/handbook/classes.html)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Classes.html)) - [ECMAScript 6 入门 - Class] [ecmascript 6 入门 - class]: http://es6.ruanyifeng.com/#docs/class ================================================ FILE: advanced/declaration-merging.md ================================================ # 声明合并 如果定义了两个相同名字的函数、接口或类,那么它们会合并成一个类型: ## 函数的合并 [之前学习过](../basics/type-of-function.md#重载),我们可以使用重载定义多个函数类型: ```ts function reverse(x: number): number; function reverse(x: string): string; function reverse(x: number | string): number | string { if (typeof x === 'number') { return Number(x.toString().split('').reverse().join('')); } else if (typeof x === 'string') { return x.split('').reverse().join(''); } } ``` ## 接口的合并 接口中的属性在合并时会简单的合并到一个接口中: ```ts interface Alarm { price: number; } interface Alarm { weight: number; } ``` 相当于: ```ts interface Alarm { price: number; weight: number; } ``` 注意,**合并的属性的类型必须是唯一的**: ```ts interface Alarm { price: number; } interface Alarm { price: number; // 虽然重复了,但是类型都是 `number`,所以不会报错 weight: number; } ``` ```ts interface Alarm { price: number; } interface Alarm { price: string; // 类型不一致,会报错 weight: number; } // 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'. ``` 接口中方法的合并,与函数的合并一样: ```ts interface Alarm { price: number; alert(s: string): string; } interface Alarm { weight: number; alert(s: string, n: number): string; } ``` 相当于: ```ts interface Alarm { price: number; weight: number; alert(s: string): string; alert(s: string, n: number): string; } ``` ## 类的合并 类的合并与接口的合并规则一致。 ## 参考 - [Declaration Merging](http://www.typescriptlang.org/docs/handbook/declaration-merging.html)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Declaration%20Merging.html)) ================================================ FILE: advanced/decorator.md ================================================ # 装饰器 写在前面:本章只介绍 TypeScript 5.0+ 的装饰器用法,对于 5.0 以下的版本,请参考 [TypeScript 官方文档](https://www.typescriptlang.org/docs/handbook/decorators.html) ## 什么是装饰器 首先,什么是装饰器呢?[维基百科](https://en.wikipedia.org/wiki/Decorator_pattern)是这么说的: > 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)). 本人的蹩足翻译:在 OOP (面向对象编程)中,装饰器模式是一种允许动态地往一个对象上添加自定义行为,而又不影响该对象所属的类的其他实例的一种设计模式。 > 什么是 OOP 和类?[前面的章节](https://ts.xcatliu.com/advanced/class.html)做过介绍。 这句话未免过于拗口了,我们不妨换个角度去切入。 ## 装饰器的使用场景 要知道,一切设计模式的诞生,都是为了解决某个问题。在 JavaScript 的世界中,装饰器通常出现于以下场景: 1. 提供一种易读且容易实现的方式,修改类或者类的方法,避免出现大量重复的代码。 下面以修改类的方法为例。 首先,假设我们有一个 `Animal` 类: ```ts class Animal { type: string constructor(type: string) { this.type = type } greet() { console.log(`Hello, I'm a(n) ${this.type}!`) } } const xcat = new Animal('cat') xcat.greet() // Hello, I'm a(n) cat! ``` 该类有一个 greet 方法,和调用方打招呼。 假如说,我还希望根据不同的 `type`,往 console 打印不同动物的叫声呢? 聪明的你或许想到了,这不就是**类的继承**吗!在子类的 `greet()` 方法中,实现不同的逻辑,再调用 `super.greet()` 即可。 ```ts class Xcat extends Animal { constructor() { super('cat') } greet() { console.log('meow~ meow~') super.greet() } } const xcat = new Xcat() xcat.greet() // meow~ meow~ // Hello, I'm a(n) cat! ``` 用装饰器实现,也不妨为一种思路,比如在 `Animal` 类中,为 `greet()` 方法添加「打印不同动物叫声的」行为: ```ts class Animal { type: string constructor(type: string) { this.type = type } @yelling greet() { console.log(`Hello, I'm a(n) ${this.type}!`) } } const typeToYellingMap = { cat: 'meow~ meow~' } function yelling(originalMethod: any, context: ClassMethodDecoratorContext) { return function(...args: any[]) { console.log(typeToYellingMap[this.type]) originalMethod.call(this, ...args) } } const xcat = new Animal('cat') xcat.greet() // meow~ meow~ // Hello, I'm a(n) cat! ``` 在 `Animal.greet()` 方法上出现的 `@yelling` ,就是 TypeScript 中装饰器的写法,即 @ + 函数名的组合。 上述示例对装饰器的应用属于**方法装饰器**,此类装饰器本身接收两个参数,一是被装饰的方法,二是方法装饰器的上下文。方法装饰器应返回一个函数,此函数在运行时真正被执行。在上述例子中,我们在装饰器返回的函数中做了两件事情: 1. 打印相应类别的动物的叫声。 2. 调用 `originalMethod.call(this, …args)` ,确保原方法(即装饰器所装饰的方法)能够正确地被执行。 2. 结合「**依赖注入**」这一设计模式,优化模块与 class 的依赖关系。 什么是依赖注入呢?引用同事 [zio](https://github.com/ziofat) 的原话: > **依赖注入其实是将一个模块所依赖的部分作为参数传入,而不是由模块自己去构造。** 可见,依赖注入解决了实际工程项目中,类、模块间依赖关系层级复杂的问题,将构造单例的行为交由实现依赖注入的框架去处理。 举个例子: ```ts @injectable class Dog implements IAnimal { sayHi() { console.log('woof woof woof') } } @injectable class Cat implements IAnimal { sayHi() { console.log('meow meow meow') } } class AnimalService { constructor( @inject dog: Dog @inject cat: Cat ) { this._dog = dog this._cat = cat } sayHiByDog() { this._dog.sayHi() } sayHiByCat() { this._cat.sayHi() } } ``` 在上述代码中,`@injectable` 将一个类标记为「可被注入的」,在面向业务的类(即 `AnimalService`)中,使用 `@inject` 注入此类的单例,实现了「依赖倒置」。注意到这里的 `implements IAnimal` 用法,也是实战中依赖注入运用的精妙之处 —— 关心接口,而非具体实现。 3. 实现「AOP」,即 Aspect-oriented programming,面向切面编程。 所谓的「切面」,可以理解成,在复杂的各个业务维度中,只关注一个维度的事务。 例如,使用装饰器,实现对类的某个方法的执行时间记录: ```ts class MyService { @recordExecution myFn() { // do something... } } function recordExecution(originalMethod: any, context: ClassMethodDecoratorContext) { return function(...args: any[]) { console.time('mark execution') originalMethod.call(this, ...args) console.timeEnd('mark execution') } } ``` ## 装饰器的类别 通过以上例子,相信读者已经对装饰器有一定了解,且认识到了装饰器在一些场景的强大之处。在此引用[阮一峰 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)稍做总结: > 装饰器是一种函数,写成`@ + 函数名`,可以用来装饰四种类型的值。 > > - 类 > - 类的属性 > - 类的方法 > - 属性存取器(accessor, getter, setter) > 装饰器的执行步骤如下。 > > 1. 计算各个装饰器的值,按照从左到右,从上到下的顺序。 > 2. 调用方法装饰器。 > 3. 调用类装饰器。 不管是哪种类型的装饰器,它们的函数签名都可以认为是一致的,即均接收 `value`, `context` 两个参数,前者指被装饰的对象,后者指一个存储了上下文信息的对象。 ## context 与 metadata 二三讲 四种装饰器的 context,均包含以下信息: - kind 描述被装饰的 value 的类型,可取 `class`, `method`, `field`, `getter`, `setter`, `accessor` 这些值。 - name 描述被装饰的 value 的名字。 - addInitializer 一个方法,接收一个回调函数,使得开发者可以侵入 value 的初始化过程作修改。 对 `class` 来说,这个回调函数会在类定义最终确认后调用,即相当于在初始化过程的最后一步。 对其他的 value 来说,如果是被 `static` 所修饰的,则会在类定义期间被调用,且早于其他静态属性的赋值过程;否则,会在类初始化期间被调用,且早于 value 自身的初始化。 以下是 `@bound` 类方法装饰器的例子,该装饰器自动为方法绑定 `this`: ```ts const bound = (value, context: ClassMemberDecoratorContext) { if (context.private) throw new TypeError("Not supported on private methods."); context.addInitializer(function () { this[context.name] = this[context.name].bind(this); }); } ``` - metadata 和装饰器类似,[metadata](https://github.com/tc39/proposal-decorator-metadata) 也是处于 stage 3 阶段的一个提案。装饰器只能访问到类原型链、类实例的相关数据,而 metadata 给了开发者更大的自由,让程序于运行时访问到编译时决定的元数据。 举个例子: ```ts function meta(key, value) { return (_, context) => { context.metadata[key] = value; }; } @meta('a', 'x') class C { @meta('b', 'y') m() {} } C[Symbol.metadata].a; // 'x' C[Symbol.metadata].b; // 'y' ``` 在上述程序中,我们通过访问类的 `Symbol.metadata` ,读取到了 meta 装饰器所写入的元数据。对元数据的访问,有且仅有这一种形式。 注意一点,metadata 是作用在类上的,即使它的位置在类方法上。想实现细粒度的元数据存储,可以考虑手动维护若干 `WeakMap`。 除了类装饰器以外,其他3种装饰器的 context 还拥有以下 3 个字段: - static 布尔值,描述 value 是否为 static 所修饰。 - private 布尔值,描述 value 是否为 private 所修饰。 - access 一个对象,可在运行时访问 value 相关数据。 以类方法装饰器为例,用 `access.get` 可在运行时读取方法值,`access.has` 可在运行时查询对象上是否有某方法,举个例子: ```ts const typeToYellingMap = { cat: 'meow~ meow~', } let yellingMethodContext: ClassMethodDecoratorContext class Animal { type: string constructor(type: string) { this.type = type } @yelling greet() { console.log(`Hello, I'm a(n) ${this.type}!`) } accessor y = 1 } function yelling(originalMethod: any, context: ClassMethodDecoratorContext) { yellingMethodContext = context return function (this: any, ...args: any[]) { console.log(typeToYellingMap[this.type as keyof typeof typeToYellingMap]) originalMethod.call(this, ...args) } } const xcat = new Animal('cat') xcat.greet() // meow~ meow~ // Hello, I'm a(n) cat! yellingMethodContext.access.get(xcat).call(xcat) // meow~ meow~ // Hello, I'm a(n) cat! console.log(yellingMethodContext.access.has(xcat)) // true ``` `getter` 类别的装饰器,其 `context.access` 同样拥有 `has`, `get` 两个方法。 对于 `setter` 类别的装饰器,则是 `has` 与 `set` 方法。 `filed` 与 `accessor` 类别的装饰器,拥有 `has`, `get`, `set` 全部三个方法。 ================================================ FILE: advanced/enum.md ================================================ # 枚举 枚举(Enum)类型用于取值被限定在一定范围内的场景,比如一周只能有七天,颜色限定为红绿蓝等。 ## 简单的例子 枚举使用 `enum` 关键字来定义: ```ts enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat}; ``` 枚举成员会被赋值为从 `0` 开始递增的数字,同时也会对枚举值到枚举名进行反向映射: ```ts enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat}; console.log(Days["Sun"] === 0); // true console.log(Days["Mon"] === 1); // true console.log(Days["Tue"] === 2); // true console.log(Days["Sat"] === 6); // true console.log(Days[0] === "Sun"); // true console.log(Days[1] === "Mon"); // true console.log(Days[2] === "Tue"); // true console.log(Days[6] === "Sat"); // true ``` 事实上,上面的例子会被编译为: ```js var Days; (function (Days) { Days[Days["Sun"] = 0] = "Sun"; Days[Days["Mon"] = 1] = "Mon"; Days[Days["Tue"] = 2] = "Tue"; Days[Days["Wed"] = 3] = "Wed"; Days[Days["Thu"] = 4] = "Thu"; Days[Days["Fri"] = 5] = "Fri"; Days[Days["Sat"] = 6] = "Sat"; })(Days || (Days = {})); ``` ## 手动赋值 我们也可以给枚举项手动赋值: ```ts enum Days {Sun = 7, Mon = 1, Tue, Wed, Thu, Fri, Sat}; console.log(Days["Sun"] === 7); // true console.log(Days["Mon"] === 1); // true console.log(Days["Tue"] === 2); // true console.log(Days["Sat"] === 6); // true ``` 上面的例子中,未手动赋值的枚举项会接着上一个枚举项递增。 如果未手动赋值的枚举项与手动赋值的重复了,TypeScript 是不会察觉到这一点的: ```ts enum Days {Sun = 3, Mon = 1, Tue, Wed, Thu, Fri, Sat}; console.log(Days["Sun"] === 3); // true console.log(Days["Wed"] === 3); // true console.log(Days[3] === "Sun"); // false console.log(Days[3] === "Wed"); // true ``` 上面的例子中,递增到 `3` 的时候与前面的 `Sun` 的取值重复了,但是 TypeScript 并没有报错,导致 `Days[3]` 的值先是 `"Sun"`,而后又被 `"Wed"` 覆盖了。编译的结果是: ```js var Days; (function (Days) { Days[Days["Sun"] = 3] = "Sun"; Days[Days["Mon"] = 1] = "Mon"; Days[Days["Tue"] = 2] = "Tue"; Days[Days["Wed"] = 3] = "Wed"; Days[Days["Thu"] = 4] = "Thu"; Days[Days["Fri"] = 5] = "Fri"; Days[Days["Sat"] = 6] = "Sat"; })(Days || (Days = {})); ``` 所以使用的时候需要注意,最好不要出现这种覆盖的情况。 手动赋值的枚举项可以不是数字,此时需要使用类型断言来让 tsc 无视类型检查 (编译出的 js 仍然是可用的): ```ts enum Days {Sun = 7, Mon, Tue, Wed, Thu, Fri, Sat = "S"}; ``` ```js var Days; (function (Days) { Days[Days["Sun"] = 7] = "Sun"; Days[Days["Mon"] = 8] = "Mon"; Days[Days["Tue"] = 9] = "Tue"; Days[Days["Wed"] = 10] = "Wed"; Days[Days["Thu"] = 11] = "Thu"; Days[Days["Fri"] = 12] = "Fri"; Days[Days["Sat"] = "S"] = "Sat"; })(Days || (Days = {})); ``` 当然,手动赋值的枚举项也可以为小数或负数,此时后续未手动赋值的项的递增步长仍为 `1`: ```ts enum Days {Sun = 7, Mon = 1.5, Tue, Wed, Thu, Fri, Sat}; console.log(Days["Sun"] === 7); // true console.log(Days["Mon"] === 1.5); // true console.log(Days["Tue"] === 2.5); // true console.log(Days["Sat"] === 6.5); // true ``` ## 常数项和计算所得项 枚举项有两种类型:常数项(constant member)和计算所得项(computed member)。 前面我们所举的例子都是常数项,一个典型的计算所得项的例子: ```ts enum Color {Red, Green, Blue = "blue".length}; ``` 上面的例子中,`"blue".length` 就是一个计算所得项。 上面的例子不会报错,但是**如果紧接在计算所得项后面的是未手动赋值的项,那么它就会因为无法获得初始值而报错**: ```ts enum Color {Red = "red".length, Green, Blue}; // index.ts(1,33): error TS1061: Enum member must have initializer. // index.ts(1,40): error TS1061: Enum member must have initializer. ``` 下面是常数项和计算所得项的完整定义,部分引用自[中文手册 - 枚举]: 当满足以下条件时,枚举成员被当作是常数: - 不具有初始化函数并且之前的枚举成员是常数。在这种情况下,当前枚举成员的值为上一个枚举成员的值加 `1`。但第一个枚举元素是个例外。如果它没有初始化方法,那么它的初始值为 `0`。 - 枚举成员使用常数枚举表达式初始化。常数枚举表达式是 TypeScript 表达式的子集,它可以在编译阶段求值。当一个表达式满足下面条件之一时,它就是一个常数枚举表达式: - 数字字面量 - 引用之前定义的常数枚举成员(可以是在不同的枚举类型中定义的)如果这个成员是在同一个枚举类型中定义的,可以使用非限定名来引用 - 带括号的常数枚举表达式 - `+`, `-`, `~` 一元运算符应用于常数枚举表达式 - `+`, `-`, `*`, `/`, `%`, `<<`, `>>`, `>>>`, `&`, `|`, `^` 二元运算符,常数枚举表达式做为其一个操作对象。若常数枚举表达式求值后为 NaN 或 Infinity,则会在编译阶段报错 所有其它情况的枚举成员被当作是需要计算得出的值。 ## 常数枚举 常数枚举是使用 `const enum` 定义的枚举类型: ```ts const enum Directions { Up, Down, Left, Right } let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right]; ``` 常数枚举与普通枚举的区别是,它会在编译阶段被删除,并且不能包含计算成员。 上例的编译结果是: ```js var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */]; ``` 假如包含了计算成员,则会在编译阶段报错: ```ts const enum Color {Red, Green, Blue = "blue".length}; // index.ts(1,38): error TS2474: In 'const' enum declarations member initializer must be constant expression. ``` ## 外部枚举 外部枚举(Ambient Enums)是使用 `declare enum` 定义的枚举类型: ```ts declare enum Directions { Up, Down, Left, Right } let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right]; ``` 之前提到过,`declare` 定义的类型只会用于编译时的检查,编译结果中会被删除。 上例的编译结果是: ```js var directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right]; ``` 外部枚举与声明语句一样,常出现在声明文件中。 同时使用 `declare` 和 `const` 也是可以的: ```ts declare const enum Directions { Up, Down, Left, Right } let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right]; ``` 编译结果: ```js var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */]; ``` > TypeScript 的枚举类型的概念[来源于 C#][C# Enum]。 ## 参考 - [Enums](http://www.typescriptlang.org/docs/handbook/enums.html)([中文版][中文手册 - 枚举]) - [C# Enum][] [中文手册 - 枚举]: https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Enums.html [C# Enum]: https://msdn.microsoft.com/zh-cn/library/sbbt4032.aspx ================================================ FILE: advanced/further-reading.md ================================================ # 扩展阅读 此处记录了[官方手册](http://www.typescriptlang.org/docs/handbook/basic-types.html)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/))中包含,但是本书未涉及的概念。 我认为它们是一些不重要或者不属于 TypeScript 的概念,所以这里只给出一个简单的释义,详细内容可以点击链接深入理解。 - [Never](http://www.typescriptlang.org/docs/handbook/basic-types.html#never)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Basic%20Types.html#never)):永远不存在值的类型,一般用于错误处理函数 - [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) - [`this`](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Functions.html#this):箭头函数的运用,这是 [ES6 的知识](http://es6.ruanyifeng.com/#docs/function) - [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#在泛型里使用类类型)):创建工厂函数时,需要引用构造函数的类类型 - [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#最佳通用类型)):数组的类型推论 - [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#上下文类型)):函数输入的类型推论 - [Type Compatibility](http://www.typescriptlang.org/docs/handbook/type-compatibility.html)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Type%20Compatibility.html)):允许不严格符合类型,只需要在一定规则下兼容即可 - [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))):使用 `&` 将多种类型的共有部分叠加成一种类型 - [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))):联合类型在一些情况下被识别为特定的类型 - [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))):使用 `|` 联合多个接口的时候,通过一个共有的属性形成可辨识联合 - [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 正确的识别为子类的实例。 - [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) - [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) - [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) 替代 - [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模式的实现) ================================================ FILE: advanced/generics.md ================================================ # 泛型 泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。 ## 简单的例子 首先,我们来实现一个函数 `createArray`,它可以创建一个指定长度的数组,同时将每一项都填充一个默认值: ```ts function createArray(length: number, value: any): Array { let result = []; for (let i = 0; i < length; i++) { result[i] = value; } return result; } createArray(3, 'x'); // ['x', 'x', 'x'] ``` 上例中,我们使用了[之前提到过的数组泛型](../basics/type-of-array.md#数组泛型)来定义返回值的类型。 这段代码编译不会报错,但是一个显而易见的缺陷是,它并没有准确的定义返回值的类型: `Array` 允许数组的每一项都为任意类型。但是我们预期的是,数组中每一项都应该是输入的 `value` 的类型。 这时候,泛型就派上用场了: ```ts function createArray(length: number, value: T): Array {   let result: T[] = []; for (let i = 0; i < length; i++) { result[i] = value; } return result; } createArray(3, 'x'); // ['x', 'x', 'x'] ``` 上例中,我们在函数名后添加了 ``,其中 `T` 用来指代任意输入的类型,在后面的输入 `value: T` 和输出 `Array` 中即可使用了。 接着在调用的时候,可以指定它具体的类型为 `string`。当然,也可以不手动指定,而让类型推论自动推算出来: ```ts function createArray(length: number, value: T): Array { let result: T[] = []; for (let i = 0; i < length; i++) { result[i] = value; } return result; } createArray(3, 'x'); // ['x', 'x', 'x'] ``` ## 多个类型参数 定义泛型的时候,可以一次定义多个类型参数: ```ts function swap(tuple: [T, U]): [U, T] { return [tuple[1], tuple[0]]; } swap([7, 'seven']); // ['seven', 7] ``` 上例中,我们定义了一个 `swap` 函数,用来交换输入的元组。 ## 泛型约束 在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法: ```ts function loggingIdentity(arg: T): T { console.log(arg.length); return arg; } // index.ts(2,19): error TS2339: Property 'length' does not exist on type 'T'. ``` 上例中,泛型 `T` 不一定包含属性 `length`,所以编译的时候报错了。 这时,我们可以对泛型进行约束,只允许这个函数传入那些包含 `length` 属性的变量。这就是泛型约束: ```ts interface Lengthwise { length: number; } function loggingIdentity(arg: T): T { console.log(arg.length); return arg; } ``` 上例中,我们使用了 `extends` 约束了泛型 `T` 必须符合接口 `Lengthwise` 的形状,也就是必须包含 `length` 属性。 此时如果调用 `loggingIdentity` 的时候,传入的 `arg` 不包含 `length`,那么在编译阶段就会报错了: ```ts interface Lengthwise { length: number; } function loggingIdentity(arg: T): T { console.log(arg.length); return arg; } loggingIdentity(7); // index.ts(10,17): error TS2345: Argument of type '7' is not assignable to parameter of type 'Lengthwise'. ``` 多个类型参数之间也可以互相约束: ```ts function copyFields(target: T, source: U): T { for (let id in source) { target[id] = (source)[id]; } return target; } let x = { a: 1, b: 2, c: 3, d: 4 }; copyFields(x, { b: 10, d: 20 }); ``` 上例中,我们使用了两个类型参数,其中要求 `T` 继承 `U`,这样就保证了 `U` 上不会出现 `T` 中不存在的字段。 ## 泛型接口 [之前学习过](../basics/type-of-function.md#接口中函数的定义),可以使用接口的方式来定义一个函数需要符合的形状: ```ts interface SearchFunc { (source: string, subString: string): boolean; } let mySearch: SearchFunc; mySearch = function(source: string, subString: string) { return source.search(subString) !== -1; } ``` 当然也可以使用含有泛型的接口来定义函数的形状: ```ts interface CreateArrayFunc { (length: number, value: T): Array; } let createArray: CreateArrayFunc; createArray = function(length: number, value: T): Array { let result: T[] = []; for (let i = 0; i < length; i++) { result[i] = value; } return result; } createArray(3, 'x'); // ['x', 'x', 'x'] ``` 进一步,我们可以把泛型参数提前到接口名上: ```ts interface CreateArrayFunc { (length: number, value: T): Array; } let createArray: CreateArrayFunc; createArray = function(length: number, value: T): Array { let result: T[] = []; for (let i = 0; i < length; i++) { result[i] = value; } return result; } createArray(3, 'x'); // ['x', 'x', 'x'] ``` 注意,此时在使用泛型接口的时候,需要定义泛型的类型。 ## 泛型类 与泛型接口类似,泛型也可以用于类的类型定义中: ```ts class GenericNumber { zeroValue: T; add: (x: T, y: T) => T; } let myGenericNumber = new GenericNumber(); myGenericNumber.zeroValue = 0; myGenericNumber.add = function(x, y) { return x + y; }; ``` ## 泛型参数的默认类型 在 TypeScript 2.3 以后,我们可以为泛型中的类型参数指定默认类型。当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用。 ```ts function createArray(length: number, value: T): Array { let result: T[] = []; for (let i = 0; i < length; i++) { result[i] = value; } return result; } ``` ## 参考 - [Generics](http://www.typescriptlang.org/docs/handbook/generics.html)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/generics.html)) - [Generic parameter defaults](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-3.html#generic-parameter-defaults) ================================================ FILE: advanced/string-literal-types.md ================================================ # 字符串字面量类型 字符串字面量类型用来约束取值只能是某几个字符串中的一个。 ## 简单的例子 ```ts type EventNames = 'click' | 'scroll' | 'mousemove'; function handleEvent(ele: Element, event: EventNames) { // do something } handleEvent(document.getElementById('hello'), 'scroll'); // 没问题 handleEvent(document.getElementById('world'), 'dblclick'); // 报错,event 不能为 'dblclick' // index.ts(7,47): error TS2345: Argument of type '"dblclick"' is not assignable to parameter of type 'EventNames'. ``` 上例中,我们使用 `type` 定了一个字符串字面量类型 `EventNames`,它只能取三种字符串中的一种。 注意,**类型别名与字符串字面量类型都是使用 `type` 进行定义。** ## 参考 - [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#字符串字面量类型)) ================================================ FILE: advanced/tuple.md ================================================ # 元组 数组合并了相同类型的对象,而元组(Tuple)合并了不同类型的对象。 元组起源于函数编程语言(如 F#),这些语言中会频繁使用元组。 ## 简单的例子 定义一对值分别为 `string` 和 `number` 的元组: ```ts let tom: [string, number] = ['Tom', 25]; ``` 当赋值或访问一个已知索引的元素时,会得到正确的类型: ```ts let tom: [string, number]; tom[0] = 'Tom'; tom[1] = 25; tom[0].slice(1); tom[1].toFixed(2); ``` 也可以只赋值其中一项: ```ts let tom: [string, number]; tom[0] = 'Tom'; ``` 但是当直接对元组类型的变量进行初始化或者赋值的时候,需要提供所有元组类型中指定的项。 ```ts let tom: [string, number]; tom = ['Tom', 25]; ``` ```ts let tom: [string, number]; tom = ['Tom']; // Property '1' is missing in type '[string]' but required in type '[string, number]'. ``` ## 越界的元素 当添加越界的元素时,它的类型会被限制为元组中每个类型的联合类型: ```ts let tom: [string, number]; tom = ['Tom', 25]; tom.push('male'); tom.push(true); // Argument of type 'true' is not assignable to parameter of type 'string | number'. ``` ## 参考 - [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)) ================================================ FILE: advanced/type-aliases.md ================================================ # 类型别名 类型别名用来给一个类型起个新名字。 ## 简单的例子 ```ts type Name = string; type NameResolver = () => string; type NameOrResolver = Name | NameResolver; function getName(n: NameOrResolver): Name { if (typeof n === 'string') { return n; } else { return n(); } } ``` 上例中,我们使用 `type` 创建类型别名。 类型别名常用于联合类型。 ## 参考 - [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#类型别名)) ================================================ FILE: basics/README.md ================================================ # 基础 本部分介绍了 TypeScript 中的常用类型和一些基本概念,旨在让大家对 TypeScript 有个初步的理解。具体内容包括: - [原始数据类型](primitive-data-types.md) - [任意值](any.md) - [类型推论](type-inference.md) - [联合类型](union-types.md) - [对象的类型——接口](type-of-object-interfaces.md) - [数组的类型](type-of-array.md) - [函数的类型](type-of-function.md) - [类型断言](type-assertion.md) - [声明文件](declaration-files.md) - [内置对象](built-in-objects.md) ================================================ FILE: basics/any.md ================================================ # 任意值 任意值(Any)用来表示允许赋值为任意类型。 ## 什么是任意值类型 如果是一个普通类型,在赋值过程中改变类型是不被允许的: ```ts let myFavoriteNumber: string = 'seven'; myFavoriteNumber = 7; // index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'. ``` 但如果是 `any` 类型,则允许被赋值为任意类型。 ```ts let myFavoriteNumber: any = 'seven'; myFavoriteNumber = 7; ``` ## 任意值的属性和方法 在任意值上访问任何属性都是允许的: ```ts let anyThing: any = 'hello'; console.log(anyThing.myName); console.log(anyThing.myName.firstName); ``` 也允许调用任何方法: ```ts let anyThing: any = 'Tom'; anyThing.setName('Jerry'); anyThing.setName('Jerry').sayHello(); anyThing.myName.setFirstName('Cat'); ``` 可以认为,**声明一个变量为任意值之后,对它的任何操作,返回的内容的类型都是任意值**。 ## 未声明类型的变量 **变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型**: ```ts let something; something = 'seven'; something = 7; something.setName('Tom'); ``` 等价于 ```ts let something: any; something = 'seven'; something = 7; something.setName('Tom'); ``` ## 参考 - [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#任意值)) ================================================ FILE: basics/built-in-objects.md ================================================ # 内置对象 JavaScript 中有很多[内置对象][],它们可以直接在 TypeScript 中当做定义好了的类型。 内置对象是指根据标准在全局作用域(Global)上存在的对象。这里的标准是指 ECMAScript 和其他环境(比如 DOM)的标准。 ## ECMAScript 的内置对象 ECMAScript 标准提供的内置对象有: `Boolean`、`Error`、`Date`、`RegExp` 等。 我们可以在 TypeScript 中将变量定义为这些类型: ```ts let b: Boolean = new Boolean(1); let e: Error = new Error('Error occurred'); let d: Date = new Date(); let r: RegExp = /[a-z]/; ``` 更多的内置对象,可以查看 [MDN 的文档][内置对象]。 而他们的定义文件,则在 [TypeScript 核心库的定义文件][]中。 ## DOM 和 BOM 的内置对象 DOM 和 BOM 提供的内置对象有: `Document`、`HTMLElement`、`Event`、`NodeList` 等。 TypeScript 中会经常用到这些类型: ```ts let body: HTMLElement = document.body; let allDiv: NodeList = document.querySelectorAll('div'); document.addEventListener('click', function(e: MouseEvent) { // Do something }); ``` 它们的定义文件同样在 [TypeScript 核心库的定义文件][]中。 ## TypeScript 核心库的定义文件 [TypeScript 核心库的定义文件][]中定义了所有浏览器环境需要用到的类型,并且是预置在 TypeScript 中的。 当你在使用一些常用的方法的时候,TypeScript 实际上已经帮你做了很多类型判断的工作了,比如: ```ts Math.pow(10, '2'); // index.ts(1,14): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'. ``` 上面的例子中,`Math.pow` 必须接受两个 `number` 类型的参数。事实上 `Math.pow` 的类型定义如下: ```ts interface Math { /** * Returns the value of a base expression taken to a specified power. * @param x The base value of the expression. * @param y The exponent value of the expression. */ pow(x: number, y: number): number; } ``` 再举一个 DOM 中的例子: ```ts document.addEventListener('click', function(e) { console.log(e.targetCurrent); }); // index.ts(2,17): error TS2339: Property 'targetCurrent' does not exist on type 'MouseEvent'. ``` 上面的例子中,`addEventListener` 方法是在 TypeScript 核心库中定义的: ```ts interface Document extends Node, GlobalEventHandlers, NodeSelector, DocumentEvent { addEventListener(type: string, listener: (ev: MouseEvent) => any, useCapture?: boolean): void; } ``` 所以 `e` 被推断成了 `MouseEvent`,而 `MouseEvent` 是没有 `targetCurrent` 属性的,所以报错了。 注意,TypeScript 核心库的定义中不包含 Node.js 部分。 ## 用 TypeScript 写 Node.js Node.js 不是内置对象的一部分,如果想用 TypeScript 写 Node.js,则需要引入第三方声明文件: ```bash npm install @types/node --save-dev ``` ## 参考 - [内置对象][] - [TypeScript 核心库的定义文件][] [内置对象]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects [TypeScript 核心库的定义文件]: https://github.com/Microsoft/TypeScript/tree/master/src/lib ================================================ FILE: basics/declaration-files.md ================================================ # 声明文件 当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。 ## 新语法索引 由于本章涉及大量新语法,故在本章开头列出新语法的索引,方便大家在使用这些新语法时能快速查找到对应的讲解: - [`declare var`](#declare-var) 声明全局变量 - [`declare function`](#declare-function) 声明全局方法 - [`declare class`](#declare-class) 声明全局类 - [`declare enum`](#declare-enum) 声明全局枚举类型 - [`declare namespace`](#declare-namespace) 声明(含有子属性的)全局对象 - [`interface` 和 `type`](#interface-和-type) 声明全局类型 - [`export`](#export) 导出变量 - [`export namespace`](#export-namespace) 导出(含有子属性的)对象 - [`export default`](#export-default) ES6 默认导出 - [`export =`](#export-1) commonjs 导出模块 - [`export as namespace`](#export-as-namespace) UMD 库声明全局变量 - [`declare global`](#declare-global) 扩展全局变量 - [`declare module`](#declare-module) 扩展模块 - [`/// `](#san-xie-xian-zhi-ling) 三斜线指令 ## 什么是声明语句 假如我们想使用第三方库 jQuery,一种常见的方式是在 html 中通过 ` // // // ` // }} // /> // ), gitalk: { clientID: '29aa4941759fc887ed4f', clientSecret: '33e355efdf3a1959624506a5d88311145208471b', repo: 'typescript-tutorial', owner: 'xcatliu', admin: ['xcatliu'], pagerDirection: 'first', }, ga: { id: 'UA-45256157-14', }, port: 8001, }; ================================================ FILE: pandoc-list.txt ================================================ README.md introduction/README.md introduction/what-is-typescript.md introduction/get-typescript.md introduction/hello-typescript.md basics/README.md basics/primitive-data-types.md basics/any.md basics/type-inference.md basics/union-types.md basics/type-of-object-interfaces.md basics/type-of-array.md basics/type-of-function.md basics/type-assertion.md basics/declaration-files.md basics/built-in-objects.md advanced/README.md advanced/type-aliases.md advanced/string-literal-types.md advanced/tuple.md advanced/enum.md advanced/class.md advanced/class-and-interfaces.md advanced/generics.md advanced/declaration-merging.md advanced/further-reading.md engineering/README.md engineering/lint.md engineering/compiler-options.md thanks/README.md ================================================ FILE: pandoc-metadata.txt ================================================ --- title: TypeScript 入门教程 author: xcatliu description: 从 JavaScript 程序员的角度总结思考,循序渐进的理解 TypeScript language: zh-CN cover-image: pandoc-cover.jpg ... ================================================ FILE: thanks/README.md ================================================ # 感谢 - 感谢[创造和维护 TypeScript 的人们](https://github.com/Microsoft/TypeScript/graphs/contributors),给我们带来了如此优秀的工具 - 感谢 [@zhongsp](https://github.com/zhongsp/) 对[官方手册的翻译](https://zhongsp.gitbooks.io/typescript-handbook/content/index.html),本书参考了大量他的翻译,能一直坚持跟进非常不容易 - 感谢 [@阮一峰](http://www.ruanyifeng.com/home.html) 老师的 [ECMAScript 6 入门](http://es6.ruanyifeng.com/),本书引用了多处 ES6 的知识 最后,感谢你阅读完本书,希望你会有所收获。 ## 下一步 - 在 [GitHub](https://github.com/xcatliu/typescript-tutorial) 上关注本书 - 阅读[官方手册](http://www.typescriptlang.org/docs/handbook/basic-types.html)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/))巩固知识 - 阅读 [Project Configuration](http://www.typescriptlang.org/docs/handbook/tsconfig-json.html)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/tsconfig.json.html)) 学习如何配置 TypeScript 工程 - 查看[官方示例](http://www.typescriptlang.org/samples/index.html),学习真实项目