一、安装和使用
- 下载 node
- 下载typescript(
npm i -g typescript
) - tsc -v 查看 typescript版本
- tsx XXX.ts 编译 ts 文件,生成对应的 js 文件
注意:如果生成了 js 文件,ts 文件有变量重复声明的报错,可以执行
tsc -init
命令,会生成一个 tsconfig.js 文件,如果有error TS2403: Subsequent variable declarations must have the same type. Variable 'AbortSignal' must be of type '{ new (): AbortSignal; prototype: AbortSignal; abort(reason?: any): AbortSignal; timeout(milliseconds: number): AbortSignal; }', but here has type '{ new (): AbortSignal; prototype: AbortSignal; }'.
,或者declare var AbortSignal
,或者Type 'Server' is not generic.
报错,可以执行npm install @types/node -D
二、类型声明
- 类型声明
· 类型声明时 TS 非常重要的一个特点
· 通过类型声明可以指定 TS 中变量(参数、形参)的类型
· 指定类型后,当为变量赋值时,TS 编译器会自动检查值是否符合类型声明,符合则赋值,否则报错
· 简而言之,类型声明给变了设置了类型,使得变量只能存储某种类型的值 - 自动类型判断
· TS 拥有自动的类型判断机制
· 当对变量的声明和赋值是同时进行的,TS 编译器会自动判断变量的类型
· 所以如果变量的声明和赋值是同时进行的,可以省略掉类型声明
1、变量
- 声明一个变量a,同时指定它的类型为 number
- 后续赋值或使用时,类型必须跟声明时的类型一致,否则会报错
- 如果变了的声明和赋值时同时进行的,可以省略类型,ts 会自动对变量进行类型检测
- 后续使用或赋值,同样要跟之前的类型一致,否则会报错
//声明一个变量a,同时指定它的类型为 number
//后续赋值或使用时,类型必须跟声明时的类型一致,否则会报错
let a : number;
a = 1;
//如果变了的声明和赋值时同时进行的,可以省略类型,ts 会自动对变量进行类型检测
//后续使用或赋值,同样要跟之前的类型一致,否则会报错
let b = true;
b = false;
2、函数
- 声明函数时,可以指定参数的类型
- 调用函数时,入参类型要跟声明时的类型一致,另外,入参个数也要和函数声明时接收的参数个数一致
//声明函数时,可以指定参数的类型
//调用函数时,入参类型要跟声明时的类型一致,另外,入参个数也要和函数声明时接收的参数个数一致
//函数声明时,括号后面也可以规定类型,该类型是函数返回值的类型
function sum (a: number, b: number): number{
return a + b
}
sum(123,456)
// sum(123,"456")·//报错
// sum(123,456,789)·//报错
三、类型
1、any和unknown的区别
let a : number;
a = 1;
let b = true;
b = false;
let e : any;
e = "12"
a = e
let f : unknown;
f = "1"
b = unknown //报错
- 任何类型的变量都可以被赋值为 any 类型,比如上面代码中的 a = e(不仅会把 e 的类型检测关掉,还会把 a 的类型检测也关掉),但是 unkown 不行,unkown类型的数据 不能赋值给其他类型的变量
- unknown 实际上是一个类型安全的 any
2、定义对象类型
//{}用来指定对象中可以包含哪些属性
//语法:{属性名:属性值,属性名:属性值}
//在属性名后加上?,表示属性是可选的,但是需要在定义时罗列出来
//使用[propname:string]:any,是上面?的简写,不需要一一罗列
//定义对象
let obj : {name: string, age? :number};
obj = {name: "张三",age: 18}
obj = {name: "张三"}
// let obj : {name: string,[propname:string]:any};
// obj = {name: "张三",age: 18, sex: "男"}
3、定义数组
//设置数组的类型声明
//数组的类型声明:
// 类型[]
// Array<类型>
// let arr: string[]; //表示字符串数组
// arr = ["1","2"]
// let arr: number[]; //表示数字类型数组
// arr = [1,2]
let arr: Array<string>; //表示字符串数组
arr = ["1","2"]
4、定义函数
//设置函数结构的类型声明:
//语法:(形参:类型,形参:类型......) => 返回值
// let d: () => any
let d: (a: number, b: number) => number
d = function (a, b) {
return a + b;
}
类型断言
可以用来告诉解析器的实际类型。
语法:
- 变量 as 类型(a as string)
- <类型>变量(a)
let a : number;
a = 1;
let f : unknown;
f = 1
a = f as number
四、编译选项
1、自动编译文件
编译文件时,使用 -w 指令后,TS 编译器会自动监视文件的变化,并在文件变化时对文件进行重新编译,生成对应的 js 文件。
示例:
$ tsc demo.ts -w //监听某个ts文件并进行编译
$ tsc -w //检测整个目录中的ts文件并进行编译
2、自动编译整个项目
- 如果直接使用 tsc 指令,则可以自动将当前项目下的所有 ts 文件编译为 js 文件
- 但是能直接使用 tsc 命令的前提是,要先在项目目录下创建 ts 的配置文件 tsconfig.json(可手动新建文件,也可使用
tsc -init
命令创建) - tsconfig.json 是一个 JSON 文件,添加配置文件后,只需要 tsc 命令即可完成对整个项目的编译
1.配置选项
1)include
- 定义希望被编译文件所在的目录
- 默认值:
["**/*"]
(** 表示任意目录,* 表示任意文件) - 示例:
{
//src目录和test目录下的文件都会被编译
"include": [
"src/**/*",
"test/**/*"
]
}
2)exclude
- 和include相反,定义需要排除在外的目录
- 默认值:
["node-modules","bower_componts","jspm_packages"]
- 示例:
{
//目录src下的所有文件都需要编译
"include": [
"src/**/*",
// "test/**/*"
],
//目录test下的所有文件都不需要编译
"exclude": [
"test/**/*"
]
}
3)extends
- 定义被继承的配置文件(相当于引入一个配置文件)
- 示例:
//当前配置的文件中会自动包含 config 目录下的 base.json 中的所有配置信息
"extends": "./configs/base"
4)files
- 指定被编译文件的列表,只有需要编译的文件少时才会用到(与 include 类似,只是 include 要配置目录,files 要配置文件)
- 示例:
"files": [
"core.ts",
"sys.ts",
...
]
5)compilerOptions
- 编译选项是配置文件中非常重要也比较复杂的配置选项。
- 在 compilerOptions 中包含多个子选项,用来完成对编译的配置
"compilerOptions": {
// target 用来指定 ts 被编译为的 ES 版本
//'es3', 'es5', 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021'...
"target": "ES5",
// module 指定要使用的模块化的规范
//“none","commonjs","amd","es6"...
"module": "commonjs",
// lib 指定代码运行时所包含的库(宿主环境)
"lib": [
"ES5",
"DOM"
],
// outDir 用来指定编译后文件所在的目录
"outDir": "./dist",
// outFile 将所有全局作用域中的代码合并到同一个文件中
// "outFile": "./dist/app.js"
// allowJs 师傅对 js 文件进行编译,默认是false
"allowJs": true,
// checkJs 是否以 ts 的标准去检查 js 代码是否符合语法规范,默认是 false
"checkJs": true,
// removeComments 生成的 js 文件是否需要移除注释
"removeComments": true,
// noEmit 执行编译过程,但是不生产编译后的文件,可用作代码检查
"noEmit": true,
// strict 下面所有严格检查的总开关
"strict": true,
// noEmitOnError 当有错误时,不生成编译后的文件
"noEmitOnError": false,
// alwaysStrict 用来设置编译后的文件是否使用严格模式
"alwaysStrict": true,
// noImplicitAny 不允许隐式的any类型
"noImplicitAny": true,
// noImplicitThis 不允许不明确类型的 this
"noImplicitThis": true,
// strictNullChecks 严格检查空值
"strictNullChecks": true
}
五、使用webpac打包ts代码
1.初始化项目
进入根目录,执行命令npm init -y
,命令执行完会自动创建一个 package.json 文件
2.下载构建工具
npm i -D webpack webpack-cli typescript ts-loader
3.根目录下创建 webpack.config.js 文件
配置打包的入口、出口和tsloader
const path = require("path")
module.exports = {
entry: "./src/main.ts",
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js",
clean: true
},
module: {
rules: [
{
test: /\.ts$/,
use: "ts-loader",
exclude: /node_modules/
}
]
},
mode: "production"
}
4.根目录创建 tsconfig.json 文件,配置基本信息
{
"compilerOptions": {
"target": "ES2015",
"module": "ES2015",
"strict": true
}
}
5.修改 package.json 文件,配置打包命令
"scripts": {
"build": "webpack"
},
6.根目录下的 src 文件中,创建 main.ts 文件
function sum (a: number, b: number): number {
return a + b
}
console.log(sum(123,456));
7.执行打包命令
npm run build
命令执行完,会在根目录下生成 dist/bundle.js 文件
六、面向对象
1、类
-
直接定义的属性是实例属性,需要通过对象的实例去访问:
let per = new Person();
per.name -
使用 static 开头的属性(方法)是静态属性(方法),或者类属性(方法),可以直接通过类去访问:
Person.job;
Person.sleep -
readonly 开头的属性表示是只读属性,无法被修改
/*
直接定义的属性是实例属性,需要通过对象的实例去访问:
let per = new Person();
per.name
使用 static 开头的属性(方法)是静态属性(方法),或者类属性(方法),可以直接通过类去访问:
Person.job;
Person.sleep
readonly 开头的属性表示是只读属性,无法被修改
*/
class Person {
name: string = "张三";
age: number = 18
readonly sex = "男"
static job: string = "程序员";
eat() {
return "吃饭";
}
static sleep() {
return "睡觉";
}
}
let per = new Person()
console.log(per.name);
console.log(per.age);
console.log(Person.job);
console.log(per.eat());
console.log(Person.sleep());
2、构造函数和 this
class Dog{
name: string
age: number
//constructor 被称为构造函数
//构造函数会在对象创建时调用
constructor(name: string, age: number) {
//示例方法中,this表示当前的示例
//在构造函数中,当前对象就是当前创建的那个对象
//可以通过 this 向新建的对象中添加属性
this.name = name
this.age = age
}
eat() {
return "吃饭"
}
}
let dog1 = new Dog("小黑",5)
let dog2 = new Dog("小白", 6)
console.log(dog1);
console.log(dog2);
3、继承
/**
* Dog extends Animal
* 此时,Animal 被称为 父类,Dog 被称为 子类
* 使用继承后,子类会拥有父类所有的方法和属性
* 使用继承,可以将多个类中共有的代码写在一个父类中
* 这样值需要写一次就可以让所有子类都同时拥有父类的属性
* 如果希望在子类中添加一些父类中没有的属性或者方法,可以在子类中直接加
* 如果在子类中添加了和父类相同的方法,则子类方法会覆盖父类的方法
* 这种子类覆盖掉父类的形式,称为 方法重写
*/
class Dog extends Animal {
talk(){
console.log("汪汪汪");
}
run() {
console.log(`${this.name}在跑`);
}
}
class Cat extends Animal {
talk(){
console.log("喵喵喵");
}
}
let dog = new Dog("小狗", 6)
let cat = new Cat("小猫", 8)
console.log(dog);
dog.talk()
dog.run()
console.log(cat);
cat.talk()
4、super 关键字
如果子类需要额外的属性,那么就要用到super(子类的构造函数中,调用父类构造函数,处理父类所需的属性)
否则,不需要用super
class Animal {
name: string
constructor(name: string) {
this.name = name
}
talk() {
console.log("动物在叫");
}
}
class Dog extends Animal {
age: number
constructor(name: string, age: number) {
//如果在子类中写了构造函数,在子类构造函数中必须对父类的构造函数进行调用
//如果不重写,默认会自动调用父类的构造函数
super(name) //调用父类的构造函数,传入父类构造函数所需要的参数
this.age = age
}
talk() {
//在类的方法中,super 就表示当前类的父类
// super.talk() //动物在叫
console.log("汪汪汪");
}
}
let dog = new Dog("小狗", 2)
console.log(dog);
dog.talk()
5、抽象类
- 以abstract开头的类是抽象类
抽象类和其他类区别不大,只是不能用来创建对象
抽象类就是专门用类被继承的类()父类 - 抽象类中还有抽象方法,也是需要以 abstract 开头,在抽象类中只做声明,没有方法体,需要在子类中定义方法
/**
* 以abstract开头的类是抽象类
* 抽象类和其他类区别不大,只是不能用来创建对象
* 抽象类就是专门用类被继承的类(父类)
*/
abstract class Animal {
name: string
constructor(name: string) {
this.name = name
}
//定义一个抽象方法
//抽象方法使用 abstract 开头,没有方法体
//抽象方法只能定义在抽象类中,子类必须对抽象方法进行重写
abstract sayHello():void;
}
class Cat extends Animal {
sayHello() {
console.log("喵喵喵");
}
}
let cat = new Cat("小猫")
6、接口
/**
* 接口用来定义一个类结构,用来定义一个类中应该包含哪些属性和方法
* 同时接口也可以当成类型声明去使用
* 它是一种规范
*/
interface myInter {
name: string,
age: number
}
//如果定义同名的接口,接口数据会合并
interface myInter {
gender: string
}
let obj: myInter = {
name: "张三",
age: 18,
gender: "男"
}
/**
* 接口可以在定义类的时候去限制类的结构
* 接口中所有的属性都不能有实际的值
* 接口只定义对象的结构,而不考虑实际值
* 接口中所有的方法都是抽象方法
*/
interface myInterface {
name: string,
sayHello():void
}
/**
* 定义类是,可以使类去实现一个接口
* 实现接口就是使用类满足接口的要求
*/
class MyClass implements myInterface {
name: string
constructor(name: string) {
this.name = name
}
sayHello() {
console.log("你好啊");
}
}
let myclass = new MyClass("张三")
console.log(myclass);
7、属性的封装
TS 可以在属性前添加属性的修饰符
- public 修饰的属性可以在任意位置(父类、子类、实例)访问(修改),是默认值
- private 私有属性,私有属性只能在类的内部访问(修改)
- 通过在类中添加方法,是得私有属性可以被外部访问
- protected 受包含的属性,只能在当前类和当前类的子类中访问(修改)
/**
* TS 可以在属性前添加属性的修饰符
* public 修饰的属性可以在任意位置(父类、子类、实例)访问(修改),是默认值
* private 私有属性,私有属性只能在类的内部访问(修改)
* - 通过在类中添加方法,是得私有属性可以被外部访问
* protected 受包含的属性,只能在当前类和当前类的子类中访问(修改)
*/
class Person {
name: string;
private _age: number;
private gender: string;
constructor(name: string, _age: number, gender: string) {
this.name = name;
this._age = _age;
this.gender = gender;
}
getGender() {
return this.gender
}
/**
* getter 方法分用来读取属性
* setter 方法用来设置属性
* 它们被称为属性的存取器
*/
get age() {
return this._age
}
set age(value: number) {
//使用 set 可以统一处理设置的值,如 name 属性则可以被任意修改,
//属性可以任意被修改会将导致对象中的数据变得非常不安全
if(value > 0) {
this._age = value
}
}
}
let per = new Person("张三", 20, "男")
per.age = 18
console.log(per.age);
console.log(per.getGender());
console.log(per);
// protected
class B {
protected num: number
constructor(num: number) {
this.num = num
}
}
class C extends B{
test() {
console.log("----"+this.num);
}
}
let c = new C(18)
c.test()
console.log(c.num); //受 protected 影响,实例无法访问 num
8、泛型
/**
* 在定义函数或类时,如果遇到类型不明确就可以使用泛型
*/
function fn<T>(a: T): T {
return a
}
//可以直接调用具有泛型的函数
fn(1); //不指定泛型,TS 可以自动对类型进行推断
fn<number>(1); //指定泛型
//泛型可以同时指定多个
function fn2<T, K>(a: T, b: K): T {
console.log(b);
return a
}
fn2("hello", 12);
fn2<string, number>("hello", 12);
interface inter {
length: number
}
//T extends inter 表示泛型 T 必须是 inter 实现类(子类)
function fn3<T extends inter>(a: T): number {
return a.length
}
class MyClass<T> {
name: T;
constructor(name: T) {
this.name = name
}
}
let mc = new MyClass<string>("张三")