一. 优势
1.在开发过程中,发现潜在的问题 2.更友好的编辑器自动提示 3.代码语义更清晰易懂
二.环境搭建
2.1 百度 node 安装教程
https://nodejs.org/en/ node 官网
2.2 查看版本检查是否安装
命令行 node -v
和 npm -v
查看版本检查是否安装
2.3 设置 vscode
打开设置搜索quote
TypeScript › Preferences: Quote Style设置成single
单引号
搜索 Tab
设置两个缩进
打开设置搜索 save
将Editor: Format On Save 勾选,会自动格式化代码
2.5 安装 TypeScript 依赖
2.5.1 全局安装 TypeScript 依赖包
打开控制台 输入 npm install typescript -g
检查是否安装成功 tsc -v
2.5.2 TypeScript- 解决(tsc 不是内部或外部命令,也不是可运行的程序或批处理文件)问题
tsc 不是内部或外部命令,也不是可运行的程序或批处理文件
解决方法:配置环境变量
**我的电脑 –> 右键 –> 属性 –> 高级系统设置 –> 高级 –> 环境变量 **
**然后使用 npm config get prefix 查找 npm 目录 **
在环境变量里的系统变量新建
变量名: NODE_PATH
变量值: D:\home.npm-global
(变量值就是找到的 npm 目录路径)
如果 node 没有配置 全局和缓存这两个文件夹
变量值: 就为 nodejs 的文件路径
这里我两个都加上了,用 ; 隔开
**然后找到 用户变量 和 系统变量 里的 path , 在末尾添加上 %NODE_PATH% **
配置完成后重新打开 dos 命令窗口,再次输入命令 就 ok 了
2.5.2 检查是否编译成功
在文件夹下新建一个 demo.ts
| interface Point { x: number y: number }
function tsDemo(data: Point) { return Math.sqrt(data.x ** 2 + data.y ** 2) }
tsDemo({ x: 1, y: 123 })
|
在控制台输入 tsc demo.ts
编译.ts
生成.js
文件
如果在 ts 文件中有报错,但是符合 js,也一样可以编译成功
三.TypeScript 中的基本类型
TypeScript 中的基本类型:
类型声明
- 类型声明是 TS 非常重要的一个特点;
- 通过类型声明可以指定 TS 中变量(参数、形参)的类型;
- 指定类型后,当为变量赋值时,TS 编译器会自动检查值是否符合类型声明,符合则赋值,否则报错;
- 简而言之,类型声明给变量设置了类型,使得变量只能存储某种类型的值;
- 语法:
| let 变量: 类型;
let 变量: 类型 = 值;
function fn(参数: 类型, 参数: 类型): 类型{ ... }
|
自动类型判断
- TS 拥有自动的类型判断机制
- 当对变量的声明和赋值是同时进行的,TS 编译器会自动判断变量的类型
- 所以如果你的变量的声明和赋值时同时进行的,可以省略掉类型声明
类型:
类型 |
例子 |
描述 |
number |
1, -33, 2.5 |
任意数字 |
string |
‘hi’, “hi”, hi |
任意字符串 |
boolean |
true、false |
布尔值 true 或 false |
字面量 |
其本身 |
限制变量的值就是该字面量的值 |
any |
* |
任意类型 |
unknown |
* |
类型安全的 any |
void |
空值(undefined) |
没有值(或 undefined) |
never |
没有值 |
不能是任何值 |
object |
{name:’孙悟空’} |
任意的 JS 对象 |
array |
[1,2,3] |
任意 JS 数组 |
tuple |
[4,5] |
元素,TS 新增类型,固定长度数组 |
enum |
enum{A, B} |
枚举,TS 中新增类型 |
number
| let decimal: number = 6; let hex: number = 0xf00d; let binary: number = 0b1010; let octal: number = 0o744; let big: bigint = 100n;
|
boolean
| let isDone: boolean = false;
|
string
| let color: string = 'blue'; color = 'red';
let fullName: string = `Bob Bobbington`; let age: number = 37; let sentence: string = `Hello, my name is ${fullName}.
I'll be ${age + 1} years old next month.`;
|
字面量
- 也可以使用字面量去指定变量的类型,通过字面量可以确定变量的取值范围
| let color: 'red' | 'blue' | 'black'; let num: 1 | 2 | 3 | 4 | 5;
|
any
| let d: any = 4; d = 'hello'; d = true;
|
unknown
| let notSure: unknown = 4; notSure = 'hello';
|
void
| let unusable: void = undefined;
|
never
| function error(message: string): never { throw new Error(message); }
|
object(没啥用)
array
| let list: number[] = [1, 2, 3]; let list: Array<number> = [1, 2, 3];
|
tuple
| let x: [string, number]; x = ['hello', 10];
|
enum
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| enum Color { Red, Green, Blue, } let c: Color = Color.Green;
enum Color { Red = 1, Green, Blue, } let c: Color = Color.Green;
enum Color { Red = 1, Green = 2, Blue = 4, } let c: Color = Color.Green;
|
类型断言
- 有些情况下,变量的类型对于我们来说是很明确,但是 TS 编译器却并不清楚,此时,可以通过类型断言来告诉编译器变量的类型,断言有两种形式:
| let someValue: unknown = "this is a string"; let strLength: number = (someValue as string).length;
|
| let someValue: unknown = "this is a string"; let strLength: number = (<string>someValue).length;
|
类型保护
1.类型断言方式
2.用 in 语法方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| interface Bird { fly: boolean; sing: () => {}; } interface Dog { fly: boolean; bark: () => {}; }
function trainAnial(animal: Bird | Dog) { if (animal.fly) { (animal as Bird).sing(); } else { (animal as Dog).bark(); } }
function trainAnialSecond(animal: Bird | Dog) { if ('sing' in animal) { animal.sing(); } else { animal.bark(); } }
|
3.用 typeof 语法方式
| function add(first: string | number, second: string | number) { if (typeof first === 'string' || typeof second === 'string') { return `${first}${second}`; } return first + second; }
|
4.用 instanceof 语法来做类型保护
| class NumberObj { count: number; } function addSecond(first: object | NumberObj, second: object | NumberObj) { if (first instanceof NumberObj && second instanceof NumberObj) { return first.count + second.count; } return 0; }
|
type.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
|
let a: number;
a = 10; a = 33;
let b: string; b = 'hello';
let c = false; c = true;
function sum(a: number, b: number): number { return a + b; }
let res = sum(123, 345);
let a3: 10;
let b4: "male" | "female"; b4 = "male"; b4 = "female";
let c4: boolean | string; c4 = true; c4 = 'hello';
let a5: { name: string } & { age: number }; a5 = {name: 'haha', age: 18};
let a61; a61 = 10; a61 = 'hello'; a61 = true;
let a62: unknown; a62 = 10; a62 = 'hello'; a62 = true;
let s: string = 'hello';
s = a61;
if (typeof a62 === "string") { s = a62; }
function fn(): void { }
function fn2(): never { throw new Error('报错了!'); }
let a65: object; a65 = {}; a65 = function () { };
let b65: { name: string, age?: number }; b65 = {name: 'haha'}; b65 = {name: '孙悟空', age: 18};
let c65: { name: string, [propName: string]: any }; c65 = {name: '猪八戒', age: 18, gender: '男'};
let d65: (a: number, b: number) => number;
d65 = function (n1, n2) { return n1 + n2 }
d65 = function (n1: number, n2: number): number { return n1 + n2 }
d65 = function (n1: number): number { return n1 }
let e66: string[]; e66 = ['a', 'b', 'c'];
let f66: number[];
let g66: Array<number>; g66 = [1, 2, 3];
let a67: [string, number]; a67 = ['hello', 123];
enum Gender { Male, Female, }
let a68: { name: string, gender: Gender }; a68 = { name: 'hello', gender: Gender.Male } console.log(a68.gender === Gender.Male);
type myType = 1 | 2 | 3 | 4 | 5; let k: myType; let l: myType; let m: myType;
k = 2;
let someValue: unknown = "this is a string"; let strLength: number = (someValue as string).length;
let strLength2: number = (<string>someValue).length;
|
四.编译选修
自动编译文件
编译文件时,使用 -w 指令后,TS 编译器会自动监视文件的变化,并在文件发生变化时对文件进行重新编译。
示例:
自动编译整个项目
如果直接使用 tsc 指令,则可以自动将当前项目下的所有 ts 文件编译为 js 文件。
但是能直接使用 tsc 命令的前提时,要先在项目根目录下创建一个 ts 的配置文件 tsconfig.json
tsconfig.json 是一个 JSON 文件,添加配置文件后,只需只需 tsc 命令即可完成对整个项目的编译
配置选项:
include
- 定义希望被编译文件所在的目录
- 默认值:[“**/*”]
示例:
| "include":["src/**/*", "tests/**/*"]
|
上述示例中,所有 src 目录和 tests 目录下的文件都会被编译
exclude
- 定义需要排除在外的目录
- 默认值:
["node_modules", "bower_components", "jspm_packages"]
示例:
| "exclude": ["./src/hello/**/*"]
|
上述示例中,src 下 hello 目录下的文件都不会被编译
extends
示例:
| "extends": "./configs/base"
|
上述示例中,当前配置文件中会自动包含 config 目录下 base.json 中的所有配置信息
files
- 指定被编译文件的列表,只有需要编译的文件少时才会用到
示例:
| "files": [ "core.ts", "sys.ts", "types.ts", "scanner.ts", "parser.ts", "utilities.ts", "binder.ts", "checker.ts", "tsc.ts" ]
|
compilerOptions
- 编译选项是配置文件中非常重要也比较复杂的配置选项
- 在 compilerOptions 中包含多个子选项,用来完成对编译的配置
项目选项:
target
- 设置 ts 代码编译的目标版本
- 可选值:
ES3(默认)、ES5、ES6/ES2015、ES7/ES2016、ES2017、ES2018、ES2019、ES2020、ESNext
- 示例:
| "compilerOptions": { "target": "ES6" }
|
- 如上设置,我们所编写的 ts 代码将会被编译为 ES6 版本的 js 代码
lib
- 指定代码运行时所包含的库(宿主环境)
- 可选值:
ES5、ES6/ES2015、ES7/ES2016、ES2017、ES2018、ES2019、ES2020、ESNext、DOM、WebWorker、ScriptHost ......
- 示例:
| "compilerOptions": { "target": "ES6", "lib": ["ES6", "DOM"], "outDir": "dist", "outFile": "dist/aa.js" }
|
module
- 设置编译后代码使用的模块化系统
- 可选值:
CommonJS、UMD、AMD、System、ES2020、ESNext、None
- 示例:
| "compilerOptions": { "module": "CommonJS" }
|
outDir
- 编译后文件的所在目录
- 默认情况下,编译后的 js 文件会和 ts 文件位于相同的目录,设置 outDir 后可以改变编译后文件的位置
- 示例:
| "compilerOptions": { "outDir": "dist" }
|
outFile
- 将所有的文件编译为一个 js 文件
- 默认会将所有的编写在全局作用域中的代码合并为一个 js 文件,如果 module 制定了 None、System 或 AMD 则会将模块一起合并到文件之中
- 示例:
| "compilerOptions": { "outFile": "dist/app.js" }
|
rootDir
- 指定代码的根目录,默认情况下编译后文件的目录结构会以最长的公共目录为根目录,通过 rootDir 可以手动指定根目录
- 示例:
| "compilerOptions": { "rootDir": "./src" }
|
allowJs
checkJs
| "compilerOptions": { "allowJs": true, "checkJs": true }
|
noEmit
sourceMap
严格检查
- strict
- 启用所有的严格检查,默认值为 true,设置后相当于开启了所有的严格检查
- alwaysStrict
- noImplicitAny
- noImplicitThis
- strictBindCallApply
- 严格检查 bind、call 和 apply 的参数列表
- strictFunctionTypes
- strictNullChecks
- strictPropertyInitialization
额外检查
- noFallthroughCasesInSwitch
- noImplicitReturns
- noUnusedLocals
- noUnusedParameters
高级
- allowUnreachableCode
- 检查不可达代码
- 可选值:
- true,忽略不可达代码
- false,不可达代码将引起错误
- noEmitOnError
tsconfig.json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| {
"include": ["./src/**/*"],
"exclude": ["./src/exclude/**/*"],
"compilerOptions": { "target": "es2015", "module": "es2015", "lib": ["es6", "dom"], "outDir": "./dist", "allowJs": true, "checkJs": true, "removeComments": true, "noEmit": false, "noEmitOnError": true,
"strict": true, "alwaysStrict": true, "noImplicitAny": true, "noImplicitThis": true, "strictNullChecks": true } }
|
五.TypeScript 打包
webpack 整合
通常情况下,实际开发中我们都需要使用构建工具对代码进行打包;
TS 同样也可以结合构建工具一起使用,下边以 webpack 为例介绍一下如何结合构建工具使用 TS;
步骤如下:
初始化项目
进入项目根目录,执行命令 npm init -y,创建 package.json 文件
下载构建工具
命令如下:
| npm i -D webpack webpack-cli webpack-dev-server typescript ts-loader clean-webpack-plugin
|
共安装了 7 个包:
- webpack:构建工具 webpack
- webpack-cli:webpack 的命令行工具
- webpack-dev-server:webpack 的开发服务器
- typescript:ts 编译器
- ts-loader:ts 加载器,用于在 webpack 中编译 ts 文件
- html-webpack-plugin:webpack 中 html 插件,用来自动创建 html 文件
- clean-webpack-plugin:webpack 中的清除插件,每次构建都会先清除目录
配置 webpack
根目录下创建 webpack 的配置文件 webpack.config.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = { optimization: { minimize: false, },
entry: './src/index.ts',
devtool: 'inline-source-map',
devServer: { contentBase: './dist', },
output: { path: path.resolve(__dirname, 'dist'), filename: 'bundle.js', environment: { arrowFunction: false, }, },
resolve: { extensions: ['.ts', '.js'], },
module: { rules: [ { test: /\.ts$/, use: { loader: 'ts-loader', }, exclude: /node_modules/, }, ], },
plugins: [ new CleanWebpackPlugin(), new HtmlWebpackPlugin({ title: 'TS测试', }), ], };
|
配置 TS 编译选项
根目录下创建 tsconfig.json,配置可以根据自己需要
| { "compilerOptions": { "target": "ES2015", "module": "ES2015", "strict": true } }
|
修改 package.json 配置
修改 package.json 添加如下配置
| { ... "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "webpack serve --open chrome.exe" "dev": "webpack --mode development", "build": "webpack --mode production" }, ... }
|
项目使用
在 src 下创建 ts 文件,并在并命令行执行npm run build
对代码进行编译;
或者执行npm start
来启动开发服务器;
Babel
除了 webpack,开发中还经常需要结合 babel 来对代码进行转换;
以使其可以兼容到更多的浏览器,在上述步骤的基础上,通过以下步骤再将 babel 引入到项目中;
虽然 TS 在编译时也支持代码转换,但是只支持简单的代码转换;
对于例如:Promise 等 ES6 特性,TS 无法直接转换,这时还要用到 babel 来做转换;
安装依赖包:
| npm i -D @babel/core @babel/preset-env babel-loader core-js
|
共安装了 4 个包,分别是:
- @babel/core:babel 的核心工具
- @babel/preset-env:babel 的预定义环境
- @babel-loader:babel 在 webpack 中的加载器
- core-js:core-js 用来使老版本的浏览器支持新版 ES 语法
修改 webpack.config.js 配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| ... module: { rules: [ { test: /\.ts$/, use: [ { loader: "babel-loader", options:{ presets: [ [ "@babel/preset-env", { "targets":{ "chrome": "58", "ie": "11" }, "corejs":"3", "useBuiltIns": "usage" } ] ] } }, { loader: "ts-loader",
} ], exclude: /node_modules/ } ] } ...
|
如此一来,使用 ts 编译后的文件将会再次被 babel 处理;
使得代码可以在大部分浏览器中直接使用;
同时可以在配置选项的 targets 中指定要兼容的浏览器版本;
六、面向对象
要想面向对象,操作对象,首先便要拥有对象;
要创建对象,必须要先定义类,所谓的类可以理解为对象的模型;
程序中可以根据类创建指定类型的对象;
举例来说:
可以通过 Person 类来创建人的对象,通过 Dog 类创建狗的对象,不同的类可以用来创建不同的对象;
定义类
| class 类名 { 属性名: 类型;
constructor(参数: 类型){ this.属性名 = 参数; }
方法名(){ .... }
}
|
示例:
| class Person { name: string; age: number;
constructor(name: string, age: number) { this.name = name; this.age = age; }
sayHello() { console.log(`大家好,我是${this.name}`); } }
|
使用类:
| const p = new Person('孙悟空', 18); p.sayHello();
|
1_class.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
|
class Person {
name: string = '孙悟空'; age: number = 18;
sayHello() { console.log('Hello 大家好!'); }
static sayHello() { console.log('Hello 大家好!'); } }
const per = new Person();
console.log(per); console.log(per.name, per.age);
per.sayHello();
Person.sayHello();
|
构造函数
可以使用 constructor 定义一个构造器方法;
注 1:在 TS 中只能有一个构造器方法!
例如:
| class C { name: string; age: number;
constructor(name: string, age: number) { this.name = name; this.age = age; } }
|
同时也可以直接将属性定义在构造函数中:
| class C { constructor(public name: string, public age: number) { } }
|
上面两种定义方法是完全相同的!
this
在类中,使用 this 表示当前对象
2_constructor.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| class Dog { name: string; age: number;
constructor(name: string, age: number) { this.name = name; this.age = age; }
bark() { console.log(this.name); } }
const dog = new Dog('小黑', 4); const dog2 = new Dog('小白', 2);
console.log(dog); console.log(dog2);
dog2.bark();
|
注 2:子类继承父类时,必须调用父类的构造方法(如果子类中也定义了构造方法)!
例如:
| class A { protected num: number; constructor(num: number) { this.num = num; } }
class X extends A { protected name: string; constructor(num: number, name: string) { super(num); this.name = name; } }
|
如果在 X 类中不调用 super 将会报错!
继承
继承时面向对象中的又一个特性
通过继承可以将其他类中的属性和方法引入到当前类中
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class Animal { name: string; age: number;
constructor(name: string, age: number) { this.name = name; this.age = age; } }
class Dog extends Animal { bark() { console.log(`${this.name}在汪汪叫!`); } }
const dog = new Dog('旺财', 4); dog.bark();
|
通过继承可以在不修改类的情况下完成对类的扩展
extends.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| (function () { class Animal { name: string; age: number;
constructor(name: string, age: number) { this.name = name; this.age = age; }
sayHello() { console.log('动物在叫~'); } }
class Dog extends Animal { run() { console.log(`${this.name}在跑~~~`); }
sayHello() { console.log('汪汪汪汪!'); } }
class Cat extends Animal { sayHello() { console.log('喵喵喵喵!'); } }
const dog = new Dog('旺财', 5); const cat = new Cat('咪咪', 3); console.log(dog); dog.sayHello(); dog.run(); console.log(cat); cat.sayHello(); })();
|
重写
发生继承时,如果子类中的方法会替换掉父类中的同名方法,这就称为方法的重写
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| class Animal { name: string; age: number;
constructor(name: string, age: number) { this.name = name; this.age = age; }
run() { console.log(`父类中的run方法!`); } }
class Dog extends Animal { bark() { console.log(`${this.name}在汪汪叫!`); }
run() { console.log(`子类中的run方法,会重写父类中的run方法!`); } }
const dog = new Dog('旺财', 4); dog.bark();
|
在子类中可以使用 super 来完成对父类的引用
super.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| (function () { class Animal { name: string;
constructor(name: string) { this.name = name; }
sayHello() { console.log('动物在叫~'); } }
class Dog extends Animal { age: number;
constructor(name: string, age: number) { super(name); this.age = age; }
sayHello() { console.log('汪汪汪汪!'); } }
const dog = new Dog('旺财', 3); dog.sayHello(); })();
|
抽象类(abstract class)
抽象类是专门用来被其他类所继承的类,它只能被其他类所继承不能用来创建实例
| abstract class Animal{ abstract run(): void; bark(){ console.log('动物在叫~'); } }
class Dog extends Animals{ run(){ console.log('狗在跑~'); } }
|
使用 abstract 开头的方法叫做抽象方法,抽象方法没有方法体只能定义在抽象类中,继承抽象类时抽象方法必须要实现;
abstract.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| (function () {
abstract class Animal { name: string
constructor(name: string) { this.name = name }
abstract sayHello(): void }
class Dog extends Animal { sayHello() { console.log('汪汪汪汪!') } }
class Cat extends Animal { sayHello() { console.log('喵喵喵喵!') } }
const dog = new Dog('旺财') dog.sayHello() })()
|
接口(Interface)
接口的作用类似于抽象类,不同点在于:接口中的所有方法和属性都是没有实值的,换句话说接口中的所有方法都是抽象方法;
接口主要负责定义一个类的结构,接口可以去限制一个对象的接口:对象只有包含接口中定义的所有属性和方法时才能匹配接口;
同时,可以让一个类去实现接口,实现接口时类中要保护接口中的所有属性;
示例(检查对象类型):
| interface Person { name: string; sayHello(): void; }
function fn(per: Person) { per.sayHello(); }
fn({ name: '孙悟空', sayHello() { console.log(`Hello, 我是 ${this.name}`); }, });
|
示例(实现):
| interface Person{ name: string; sayHello():void; }
class Student implements Person{ constructor(public name: string) { }
sayHello() { console.log('大家好,我是'+this.name); } }
|
interface.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| (function () { type myType = { name: string, age: number, };
interface myInterface { name: string; age: number; }
interface myInterface { gender: string; }
interface myInter { name: string;
sayHello(): void; }
class MyClass implements myInter { name: string;
constructor(name: string) { this.name = name; }
sayHello() { console.log('大家好~~'); } } })();
|
封装
对象实质上就是属性和方法的容器,它的主要作用就是存储属性和方法,这就是所谓的封装
默认情况下,对象的属性是可以任意的修改的,为了确保数据的安全性,在 TS 中可以对属性的权限进行设置
- 静态属性(static):
- 声明为 static 的属性或方法不再属于实例,而是属于类的属性;
- 只读属性(readonly):
- 如果在声明属性时添加一个 readonly,则属性便成了只读属性无法修改
- TS 中属性具有三种修饰符:
- public(默认值),可以在类、子类和对象中修改
- protected ,可以在类、子类中修改
- private ,可以在类中修改
示例:
静态属性(static)
静态属性(方法),也称为类属性。使用静态属性无需创建实例,通过类即可直接使用
静态属性(方法)使用 static 开头
示例:
| class Tools { static PI = 3.1415926;
static sum(num1: number, num2: number) { return num1 + num2; } }
console.log(Tools.PI); console.log(Tools.sum(123, 456));
|
只读属性(readonly)
如果在声明属性时添加一个 readonly,则属性便成了只读属性无法修改
|
readonly name: string = '孙悟空';
|
TS 中属性具有三种访问修饰符
public(默认值),可以在类、子类和对象中修改
public:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class Person{ public name: string; public age: number;
constructor(name: string, age: number){ this.name = name; this.age = age; }
sayHello(){ console.log(`大家好,我是${this.name}`); } }
class Employee extends Person{ constructor(name: string, age: number){ super(name, age); this.name = name; } }
const p = new Person('孙悟空', 18); p.name = '猪八戒';
|
protected ,可以在类、子类中修改
protected:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| class Person{ protected name: string; protected age: number;
constructor(name: string, age: number){ this.name = name; this.age = age; }
sayHello(){ console.log(`大家好,我是${this.name}`); } }
class Employee extends Person{
constructor(name: string, age: number){ super(name, age); this.name = name; } }
const p = new Person('孙悟空', 18); p.name = '猪八戒';
|
private ,可以在类中修改
private:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| class Person{ private name: string; private age: number;
constructor(name: string, age: number){ this.name = name; this.age = age; }
sayHello(){ console.log(`大家好,我是${this.name}`); } }
class Employee extends Person{
constructor(name: string, age: number){ super(name, age); this.name = name; } }
const p = new Person('孙悟空', 18); p.name = '猪八戒';
|
属性存取器
对于一些不希望被任意修改的属性,可以将其设置为 private
直接将其设置为 private 将导致无法再通过对象修改其中的属性
我们可以在类中定义一组读取、设置属性的方法,这种对属性读取或设置的属性被称为属性的存取器
读取属性的方法叫做 setter 方法,设置属性的方法叫做 getter 方法
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class Person{ private _name: string;
constructor(name: string){ this._name = name; }
get name(){ return this._name; }
set name(name: string){ this._name = name; }
}
const p1 = new Person('孙悟空');
console.log(p1.name);
p1.name = '猪八戒';
|
泛型(Generic)
定义一个函数或类时,有些情况下无法确定其中要使用的具体类型(返回值、参数、属性的类型不能确定);
此时泛型便能够发挥作用;
举个例子:
| function test(arg: any): any{ return arg; }
|
上例中,test 函数有一个参数类型不确定,但是能确定的时其返回值的类型和参数的类型是相同的;
由于类型不确定所以参数和返回值均使用了 any,但是很明显这样做是不合适的:
首先使用 any 会关闭 TS 的类型检查,其次这样设置也不能体现出参数和返回值是相同的类型;
泛型函数
创建泛型函数
| function test<T>(arg: T): T{ return arg; }
|
这里的就是泛型;
T 是我们给这个类型起的名字(不一定非叫 T),设置泛型后即可在函数中使用 T 来表示该类型;
所以泛型其实很好理解,就表示某个类型;
那么如何使用上边的函数呢?
使用泛型函数
方式一(直接使用):
test(10)
使用时可以直接传递参数使用,类型会由 TS 自动推断出来,但有时编译器无法自动推断时还需要使用下面的方式
方式二(指定类型):
test<number>(10)
也可以在函数后手动指定泛型;
函数中声明多个泛型
可以同时指定多个泛型,泛型间使用逗号隔开:
| function test<T, K>(a: T, b: K): K{ return b; }
test<number, string>(10, "hello");
|
使用泛型时,完全可以将泛型当成是一个普通的类去使用;
泛型类
类中同样可以使用泛型:
| class MyClass<T>{ prop: T;
constructor(prop: T){ this.prop = prop; } }
|
泛型继承
除此之外,也可以对泛型的范围进行约束
| interface MyInter{ length: number; }
function test<T extends MyInter>(arg: T): number{ return arg.length; }
|
使用 T extends MyInter 表示泛型 T 必须是 MyInter 的子类,不一定非要使用接口类和抽象类同样适用;
如何使用泛型作为一个具体的类型注解
| function hello<T>(params: T) { return params; } const func: <T>(param: T) => T = hello;
|
七、高级用法
命名空间 namespace
提供一种类似模块化的编程方式,尽可能少的减少全局变量或者是把一组相关的内容封装到一起,提供统一的暴露接口
同一个文件内的命名空间
在 demo1\src\page.ts 文件中,声明namespace
,将三个类封装到到一起,最后暴露Page
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| namespace Home { class Header { constructor() { const elem = document.createElement('div'); elem.innerText = 'this is Header'; document.body.appendChild(elem); } } class Content { constructor() { const elem = document.createElement('div'); elem.innerText = 'this is Content'; document.body.appendChild(elem); } } class Footer { constructor() { const elem = document.createElement('div'); elem.innerText = 'this is Footer'; document.body.appendChild(elem); } }
export class Page { constructor() { new Header(); new Content(); new Footer(); } } }
|
在 demo1\dist\page.js 中自动生成 js 文件
在 demo1\index.html 中,调用 page.js 文件,并且调用Home
中的Page
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" />
<title>Document</title> <script src="./dist/page.js"></script> </head> <body> <script> new Home.Page(); </script> </body> </html>
|
最后打开浏览器查看效果,打开控制台输入Home.
就能找到 Page
多个文件的命名空间互相调用以及子命名空间、接口调用
先删除原来的 dist 文件
在 demo1\tsconfig.json 文件中,更改配置文件"outFile": "./dist/page.js"
将生成的文件统一打包成一个文件,生成的文件不支持commonjs
格式,修改"module": "amd"
,改成 amd 格式
| ... "module": "amd" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, "outFile": "./dist/page.js" /* Concatenate and emit output to single file. */, "outDir": "./dist" /* Redirect output structure to the directory. */, "rootDir": "./src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */, ...
|
在 demo1\src\components.ts 文件中,使用命名空间并暴露原来的三个类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| namespace Components { export class Header { constructor() { const elem = document.createElement('div'); elem.innerText = 'this is Header'; document.body.appendChild(elem); } } export class Content { constructor() { const elem = document.createElement('div'); elem.innerText = 'this is Content'; document.body.appendChild(elem); } } export class Footer { constructor() { const elem = document.createElement('div'); elem.innerText = 'this is Footer'; document.body.appendChild(elem); } } }
|
在 demo1\src\page.ts 文件中,也使用命名空间,并通过new Components.Header();
的方式调用Components
的类
使用///<reference path="./components.ts" />
,表明调用文件的关系
///<reference path="" />
是固定格式///
不能省略
|
namespace Home { export class Page { user: Components.User = { name: 'dell', };
constructor() { new Components.Header(); new Components.Content(); new Components.Footer(); } } }
|
在浏览器控制台输入Components.SubComponents.Test
就可以找到子命名空间
| Components.SubComponents.Test ƒ Test() { }
|
使用import
导入对应的模块 ES6 语法
使用 es6 语法通过import
导入模块替换///<reference path="./components.ts" />
在 demo1\src\page.ts 中,用import
以及se6
的语法来导入模块,并暴露export default class Page
| import { Header, Content, Footer } from './components';
export default class Page { constructor() { new Header(); new Content(); new Footer(); } }
|
在 demo1\src\components.ts 中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| export class Header { constructor() { const elem = document.createElement('div'); elem.innerText = 'this is Header'; document.body.appendChild(elem); } } export class Content { constructor() { const elem = document.createElement('div'); elem.innerText = 'this is Content'; document.body.appendChild(elem); } } export class Footer { constructor() { const elem = document.createElement('div'); elem.innerText = 'this is Footer'; document.body.appendChild(elem); } }
|
在 demo1\index.html 中,通过require
使用page
,其中还要引入 <script src="[https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.js"></script>](https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.js"></script>)
,
否则会报错
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" />
<title>Document</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.js"></script> <script src="./dist/page.js"></script> </head> <body> <script> require(['page'], function (page) { new page.default(); }); </script> </body> </html>
|
parcel 打包
parcel
官方文档https://github.com/parcel-bundler/parcel
将浏览器无法运行的 ts 文件编译才可以运行的 js 文件
在初始文件中
demo2\src\page.ts
| const teacher: string = 'dell'; console.log(teacher);
|
demo2\src\index.html
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <script src="./page.ts"></script> </head> <body></body> </html>
|
直接使用 <script src="./page.ts"></script>
,浏览器会报错,浏览器无法识别 ts 代码
| Uncaught SyntaxError: Missing initializer in const declaration
|
所以我们使用parcel
,自动类型转换
安装 npm install parcel@next -D
在 demo2\package.json 中更改 "test": "parcel ./src/index.html"
| { "name": "demo2", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "parcel ./src/index.html" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "parcel": "^2.0.0-beta.3.1" } }
|
不要再中文文件夹下运行,会运行失败的
最后运行 npm run test
| > demo2@1.0.0 test E:\demo2 > parcel ./src/index.html
ℹ Server running at http://localhost:1234 √ Built in 7.52s
|
运行成功,打开地址到浏览器即可预览