基础入门
1. 类型
你应该声明类型,只要存在变量的地方
1.1 变量的声明和赋值
1.1.1 声明 后 赋值
首先声明变量和它的类型,然后进行赋值,赋值只能采用你声明的类型,否则会报错。
let a: number
a = 1
1.1.2 声明 + 赋值
声明的时候,同时赋值,且只能赋值声明的类型
let c: string = "nihao"
1.1.3 直接赋值
TS会自动识别赋值的类型,然后将变量的类型设置为对应的类型
let d = 1233
1.2 函数参数类型
类似于Python声明参数的类型是什么,返回值的类型是什么。
与上面类似,如果类型不一致会报错。
function sum(a: number, b: number): number{
console.log(a + b)
return a + b
}
1.3 类型的种类
1.3.1 类型分类
1.3.2 特殊类型详解
字面量
// 1. 使用字面量来设置类型
let a: "male" // 表示a的类型为male,且值只能为"male"
a = "male"
a = "123" 错误 ×
any
表示任意类型 等价于 js默认的变量
相当于关闭类型检测;
最好避免
- any的表示方式
// 显式any
let c: any
c = 1
c = "aa"
c = true
// 隐式any
let d; // 解析器识别为该变量类型为any
-
any的弊端之一
any类型的变量可以赋值给任意类型的变量并且不报错
let c: any c = 1 let d: string d = c // 不报错返过来时可以的,任意类型可以赋值到any类型,因为any本身的类型就决定了这个性质!
unknown
一种安全的any
-
像any一样使用
let e: unknown e = 1 e = "a" e = true -
比any更安全的赋值给其他变量
不能直接赋值,尽管e的类型实际上和f相同。
let f: number = 1 f = e // 不能将类型unknown分配给类型numbere = f 是可以的,理由同上
-
实现unknown的赋值-> 类型断言
void
-
在变量类型声明时,表示undefined
let k: void k = undefined k = null // 报错 -
用于函数返回值的声明
返回为空,或者不返回,或者返回undefined都行 返回null也不对
function fn(): void{ return }
object
- 直接用,比较少:
let a: object;
- 字面量+对象,来设置对象的属性和属性的类型
let b: {
name: string,
};
b = {
name: "a"
}
// 不合法:
b = {}
b = {name: 1}
-
其他常用的
?:-
可选属性,age写与不写都行
let b: { name: string, age?: number }; -
必须有指定属性,其他有没有都行
let c: {name: string, [propName: string]: any}
-
-
设置函数的类型声明
整体上就是变量的类型声明,但是是作用到函数上;
有个疑问,如果是写和上面类似,任意多个参数的怎么写?
let d: (a: number, b: number) => number; d = function(n1:number, n2:number): number{ return n1 + n2 }
array
设置数组的类型
-
类型后+[]
- let f: string[];
- let f: string[] = ["1", "2"]
-
Array<类型名>
let i: Array<string>
tuple
固定长度+指定类型的数组
语法:[类型, 类型]
let h: [string, number]
h = ["hello", 1]
enum
枚举类型用来方便选择的
enum Gender{
Male,
Female
}
let i: {name: string, gender: Gender}
i = {
name: "孙悟空",
gender: Gender.Male
}
1.3.3 一些特殊的用法
类型相或 | :联合类型
表示类型的并集,多重类型可以使用
let b: "male" | "famale"
b = "male" √
b = "famale" √
b = "c" ×
// 不同常规类型
let a: "string" | "number"
类型断言
在unknown类型的赋值时,尽管变量e的类型已知,但是还是不能赋值给其他类型。
我们这时候可以给编辑器解释当前变量的类型:类型断言
方式1:变量 as 类型
let g: unknown
g = 1
let h: number
h = g as number
方式2:<类型名>变量
let l: number
l = <number>g
类型别名
type MyType = string
type MyNumber = 1 | 2 | 3 | 4
let l: MyNumber;
2. 编译选项
面向对象
1. 类
创建类
定义类
class Person{
}
定义属性
实例属性
class Person{
name: string = "孙悟空";
}
const per = new Person()
- 通过对象实例来访问
静态属性
属性前面 + static
class Person{
// 对象包含两个部分:
// 属性
// 实例属性: 通过实例来访问
name: string = "孙悟空";
// 在属性前使用static关键字可以定义类属性(静态属性)
static age: number = 18
// 方法
}
const per = new Person()
console.log(per)
console.log(Person.age)
- 通过类访问(Person.age)访问
- 打印per只会显示name属性
只读属性
- randonly 属性名
- randonly static 属性名
class Person {
readonly name: string = "孙悟空";
static age: number = 18
}
const per = new Person()
per.name = "猪八戒"
报错:无法为name赋值,因为它是只读的。
定义方法
实例方法
实例调用
class Person {
name: string = "孙悟空";
age: number = 18
sayHello(){
console.log("hello 大家好!")
}
}
const per = new Person()
per.name = "猪八戒"
静态方法
类调用
class Person {
name: string = "孙悟空";
age: number = 18
static sayHello(){
console.log("hello 大家好!")
}
}
Person.sayHello()
2. 构造函数
构造函数用于动态地为类中属性赋值
基本使用
- 在类中定义属性
- constructor中为this赋值
- 创建实例的时候,传递参数
class Dog{
name: string
age: number
constructor(name: string, age: number){
this.name = name
this.age = age
console.log("构造函数执行了")
}
}
const dog = new Dog("小黑", 2)
const dog1 = new Dog("小bai", 2)
console.log(dog)
console.log(dog1)
- 在构造函数中,当前对象就是当前新建的对象
- 创建谁,this就是谁
- 通过this向新建的对象中添加属性
3. 继承
和其他语言类似,继承可以复用父类的代码
class Animal {
name: string;
age: number;
test = '这是一个测试'
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("旺财", 19)
const cat = new Cat("咪咪", 29)
dog.sayHello()
cat.sayHello()
dog.run()
使用extends进行继承
Dog类继承Animal类
class Dog extends Animal
子类使用父类的属性和方法
const dog = new Dog("旺财", 19)
const cat = new Cat("咪咪", 29)
console.log(cat.test)
子类添加和重写方法
直接添加、修改即可
class Dog extends Animal {
run() {
console.log(`${this.name}在跑`)
}
sayHello() {
console.log('汪汪汪')
}
}
dog.sayHello() // 汪汪汪
dog.run() // 旺财在跑
使用super调用父类
简单调用方法
该例中,super可以理解为父类(父类的实例),因此当我们调用dog.sayHello()时,实际上是调用了父类的sayHello
class Dog extends Animal{
sayHello(): void {
// super,表示父类的实例
super.sayHello()
}
}
const dog = new Dog('tony', 19)
console.log(dog)
在构造函数中调用super
当我们想在子类中添加新的属性的时候,需要使用constructor来为动态为属性赋值。但是如果直接使用constructor,会导致重写父类的constructor。
为了解决上述问题,我们需要再子类的constructor中,调用父类,也就是super(),并且要传入父类构造函数需要的参数。
class Dog extends Animal{
age: number // 新属性
constructor(name: string, age: number){
super(name) // 调用父类
this.age = age // 为属性赋值
}
sayHello(): void {
super.sayHello()
}
}
const dog = new Dog('tony', 19)
console.log(dog) // Dog {name: 'tony', age: 19}
抽象类
抽象类:
- 抽象类不能用来创建实例对象,只用来被继承;
- 子类中的super可以使用
抽象方法:
- 抽象方法使用abstract开头,没有方法体
- 只能定义在抽象类中,子类必须对抽象方法进行重写
定义抽象类和抽象方法
abstract class Animal {
name: string;
constructor(name: string) {
this.name = name
}
abstract sayHello(): void
}
继承抽象类并重写
class Dog extends Animal{
sayHello(): void {
console.log('旺旺汪')
}
}
为什么要定义呢?直接重写不就得了?
- 定义抽象类的抽象方法可以起到一种约束作用,即强制要求子类实现这些方法,以确保每个子类都具有相同的行为和特性,从而提高代码的可维护性和可读性。
4. 接口
接口用于规定类的结构,也就是类中要包括哪些属性和方法。
也可以用于对象的结构定义
接口的定义
interface myInter {
name: string;
sayHello(): void;
}
要求:
- 接口中的所有属性都不能有实际的值
- 接口中所有的方法都是抽象方法(没有方法体)
接口的使用-类
只需要按照接口要求,编写属性和方法即可。
使用implements关键字
class MyClass implements myInter {
name: string;
constructor(name: string){
this.name = name
}
hello: string = "hell0"
sayHello(): void {
throw new Error("Method not implemented.");
}
}
接口的使用-对象
定义接口之后,可以将其作为对象的类型,其中对象的属性和方法必须是满足接口要求,同时不能添加或者删除属性(方法)。
interface MyInter {
name: string,
age: number,
sayHello(): void
}
let inter: MyInter = {
name: "111",
age: 19,
sayHello: () => {console.log(1)}
}
5. 属性的封装
使用修饰符来配置属性,使得属性在指定的地方被读取和修改;
public
public是默认的修饰符,可以在任意位置访问(修改),包括子类;
private
private:只能在当前类内部进行访问(修改),子类中也不能访问和修改
class Person {
private _name: string
private _age: number
constructor(name: string, age: number) {
this._name = name
this._age = age
}
const person = new Person()
console.log(person._name) // 报错,提示为私有属性
protected
protected: 只能在当前类和子类中使用(修改)
class A{
number: number
constructor(number: number){
this.number = number
}
}
class B extends A{
sayHello(){
console.log(this.number)
}
}
console.log(n.number) // 报错
n.sayHello() // 可以打印
TS中设置getter和setter
其实和js没什么区别
- 使用
get attr(){}访问属性attr的值 - 使用
set attr(value){}设置属性attr的值
class Person {
private _name: string;
private _age: number;
constructor(name: string, age: number) {
this._name = name
this._age = age
}
get name(): string {
console.log('get name执行了')
return this._name
}
set name(val: string) {
this._name = val
}
get age(): number{
return this._age
}
set age(val){
if (val >= 0){
this._age = val
}
}
}
console.log(person.name)
person.name = "猪八戒"
console.log(person.name)
person.age = -44
console.log(person.age)
简写类的语法糖
不用额外再写属性的定义和类型
class C{
constructor(public name: string, public age: number){
this.name = name
this.age = age
}
}
等价于:
class C{
name: string
age: number
constructor(name: string, age: number){
this.name = name
this.age = age
}
}
6. 泛型
定义一个函数或类时,有些情况下无法确定其中要使用的具体类型(返回值、参数、属性的类型不能确定),此时泛型便能够发挥作用。
为什么需要泛型
-
function test(arg: any): any{ return arg; } - 上例中,test函数有一个参数类型不确定,但是能确定的时其返回值的类型和参数的类型是相同的,由于类型不确定所以参数和返回值均使用了any,但是很明显这样做是不合适的,首先使用any会关闭TS的类型检查,其次这样设置也不能体现出参数和返回值是相同的类型
使用泛型
基本使用
-
function test<T>(arg: T): T{ return arg; } -
这里的
<T>就是泛型,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的子类,不一定非要使用接口类和抽象类同样适用。