Ts 的学习

安装ts

1
npm i typescript -g

初始化ts

1
tsc --init 

下载一个包?

1
npm i ts-node -g
1
npm i @types/node -D

类型

image-20231104195206643

顶级类型 any unknown object

Number String Boolean

number string boolean

对象是大写 实现是小写

unknown 只能赋给自身,或者是any

object Object

object 只能用引用类型?不支持原始类型

{}类型无法做修改

interface

自定义类型

interface Z 后面跟着打要大写

interface Z{

name : string

age:number

}

可以在字段前面加一个 readonly 只可以读

接口可以继承接口

extend 关键字

基本类型都差不多 讲一下 不常见的

1

交叉类型

类型断言

1
2
3
4
let fn = function(num:number | string):void{
console.log((num as string).length)
}
fn(12345)

断言 把某些东西转换成某些类型。

内置对象

Number Date RegExp 等等。

dom对象 bom

class 关键字

抽象类

抽象类 只能描述,不能定义

抽象类可以被其他类实现

元组
枚举

enum

never 类型
symbol类型

类型注解

作用:相当于强类型语言中的类型声明

语法:(变量/函数):type

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
let bool:boolean=true
let num:number = 123
let str: string = 'abc'

//数组
let arr1: number[] = [1,2,3]
let arr2: Array<number> =[1,2,3]

//数组里面加 | 可以混杂其他类型
let arr2: Array<number | string> = [1,2,3,'4']

//元组
let tuple: [number,string] = [0,'1']
// 元组长度不能改变?

//函数
add = (x:number,y:number):number => x + y
// 可以在括号后面加返回值类型,其实可以省略,他会自动类型推断

// 对象
let obj: {x: number,y:number} = {x:1,y:2}
obj.x = 3

//symbol
let s1: symbol = Symbol()
let s2 = Symbol()

//undefined null
let un: undefine = underfined
let nu: null = null

//void
let noReturn = () => {}

//any
let x

//never 永远不会有返回值
let error = () =>{
throw new Error('error')
}

let endless = () =>{
while(true){}
}

有关枚举类型

image-20231104203354890

枚举:一组有名字的常量集合

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
// 数字枚举
enum Role{
Reporter,
Developer,
Maintainer,
Owner,
Guest
}
// 可以自定义初始值 后面的相对应递增。
console.log(Role)

//字符串枚举
enum Message{
Success = '恭喜',
Fail = '抱歉'
}
//异构枚举
enum Answer{
N,
Y = 'Yes'
}

// 枚举成员
Role.Reporter = 2 // 错误 枚举成员是只读类型

//枚举成员两种类型
// const menber 常量
enum Char{
// 编译的时候计算结果,以常量出现
a,
b = Char.a,
c = 1+ 3,
// computed 不会再编译的时候的运算
d = Math.random(),
e = '123'.Length
// 注意 computed 后面 继续的枚举 要赋值,负责报错
}

//常量枚举
const enum Month{
Jan,
Feb,
Mar
}
// 不需要对象 只需要值
let month = [Month.Jan,Month.Feb,Month.Mar]

// 枚举类型
enum E {a,b}
enum F {a=0,b=1}
enum G {a = 'apple',b = 'banana'}

let e: E = 3
let f: F = 3
// e 和 f 不可以直接比较 他们是不同的枚举类型。

接口

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
interface List{
id: number;
name: string;
//签名
[x:string];any
}

interfave Result{
data List[]
}

function render(result: Result){
result.data.forEach((value) => {
console.log(value.id,value.name)
})
}

let result = {
data:[
{id:1,name:'A'}
{id:2,name:'B'}
]
}
render(result)

// 动态语言 鸭子类型 如果直接复制给对象则会报错,比如说赋值给render 就不行了 如何绕过类型检查 第一种 声明一个变量,第二个类型断言 “ as type ” 要么在对象后面加一个<type>。 要么使用字符串签名。


//readonly 只读属性 不允许被修改


// 签名
[x:string]:string
// 数字索引签名返回值一定要是 字符串索引签名返回值的子类型,因为JavaScript 默认会将number 类型转换成string ? 视频

另外问了下newbing 关于索引签名的事情

涉及到TypeScript中的索引签名(index signature)的用法和规则。索引签名是一种用来描述对象的结构的类型注解,它指定了对象的键和值的类型。索引签名有两种形式,一种是字符串索引签名,另一种是数字索引签名。字符串索引签名的形式是[key: string]: ValueType,表示对象的键是字符串类型,值是ValueType类型。数字索引签名的形式是[key: number]: ValueType,表示对象的键是数字类型,值是ValueType类型。

在你的例子中,你定义了一个接口MyObject,它有两个索引签名,一个是字符串索引签名,一个是数字索引签名。这样的接口是合法的,但是有一些限制和约束。首先,数字索引签名的值类型必须是字符串索引签名的值类型的子类型或相同类型。这是因为JavaScript会将数字类型的键转换为字符串类型的键,所以数字索引签名实际上是字符串索引签名的一种特殊情况。如果数字索引签名的值类型和字符串索引签名的值类型不一致,那么就会出现类型不兼容的错误。例如,你的第一个例子中,数字索引签名的值类型是number,而字符串索引签名的值类型是string,这就不满足子类型或相同类型的要求,所以会报错。你的第二个例子中,数字索引签名的值类型和字符串索引签名的值类型都是string,这就满足了要求,所以不会报错。

其次,如果一个接口有两个索引签名,那么它不能有其他的属性。这是因为索引签名表示对象的结构是不确定的,它可以有任意数量和类型的键,所以不能再添加其他的确定的属性。如果一个接口有其他的属性,那么它只能有一个索引签名,而且这个索引签名的值类型必须包含或相同于其他属性的值类型。例如,下面的接口是合法的,因为它只有一个索引签名,而且这个索引签名的值类型是string | number,它包含了其他属性的值类型:

interface MyObject { [key: string]: string | number; name: string; age: number; }

大致是这样子的 必须包括 或者相等 下面是newbing 生成的一首诗。

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
// TypeScript is a poem by Bing
type Poem = {
title: string;
author: string;
content: string[];
};

let TypeScript: Poem = {
title: "TypeScript",
author: "Bing",
content: [
"TypeScript is a superset of JavaScript,",
"It adds types and interfaces to the script,",
"It helps you write code that is clear and robust,",
"It catches errors and bugs before they erupt.",
"",
"TypeScript is a language that compiles to JavaScript,",
"It runs on any browser or platform that supports it,",
"It offers features like classes, generics and modules,",
"It makes your code more readable, reusable and scalable.",
"",
"TypeScript is a poem that expresses my admiration,",
"It is a tribute to the developers who created this innovation,",
"It is a challenge to the programmers who want to learn this creation,",
"It is a invitation to the readers who want to enjoy this narration."
]
};

console.log(TypeScript.title + " by " + TypeScript.author);
console.log(TypeScript.content.join("\n"));

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
interface Lib{
():void;
verision:string;
doSomething():void;
}
function getLib(){
let lib:Lib = (() => {}) as Lib;
lib.version = '1.0';
lib.doSomething = () => {}
return lib;
}
// let lib: Lib = (() => {} ) as Lib 是啥意思没看懂

// 答:在这段代码中,`let lib: Lib = (() => {}) as Lib` 是在创建一个名为 `lib` 的变量,并将其类型标记为 `Lib`。这行代码的作用是将一个匿名函数表达式(`() => {}`)强制类型转换为 `Lib` 类型。

/*让我解释一下这个代码的每个部分:

1. `let lib: Lib`:这部分定义了一个名为 `lib` 的变量,并指定了它的类型为 `Lib`。这意味着 `lib` 变量必须符合 `Lib` 接口的定义。

2. `(() => {})`:这是一个匿名函数表达式,它代表一个空函数,不执行任何操作。在这里,它被用作一个立即执行的函数,也就是在声明后立即执行。

3. `as Lib`:这是一个类型断言,它告诉 TypeScript 编译器将 `(() => {})` 这个函数表达式视为 `Lib` 类型,即满足 `Lib` 接口的定义。虽然 `(() => {})` 不满足 `Lib` 接口的定义,但是通过类型断言,你告诉编译器将其视为 `Lib` 类型,因此不会出现类型错误。

综合起来,这行代码创建了一个名为 `lib` 的变量,并将其初始化为一个空的匿名函数,然后将其类型标记为 `Lib`,以便后续可以将属性 `version` 和方法 `doSomething` 添加到 `lib` 变量中,使其符合 `Lib` 接口的定义。这是一种用于创建对象的方式,通常用于实现一种接口或类的结构。

函数

4种定义函数

1
2
3
4
5
6
7
8
9
10
function add1(x:number,y:number){
return x + y
}
let add2:(x:number,y:number) => number

type add3 = (x:mumber,y:number) => number

interface add4{
(x:number,y:number):number
}

后面三种 只是定义并没有实现。

函数可以加可选参数,但是可选参数必须在必选参数后面。或者可以给参数提供一个默认值。

1
function add6(x:number,y=0,z:number,q=1)

如果默认参数 在需要传参的前面,那么必须传入一个underfine,否则会报错。如果在后面则无所谓。

函数重载

同一个函数名字,但是对应参数不一样。或者是返回值

1
2
3
4
5
6
7
8
9
10
11
function add8(...rest:number[]):number;
function add8(...rest:string[]:string;)
function add8(...rest:any[]): any{
leet firstt = rest[0]
if (typeof first === 'string'){
return rest.join('')
}
if(typeof first === 'number'){
return rest.reduce((pre,cur) => pre + cur )
}
}

类的实现
1
2
3
4
5
6
7
8
9
10
class Dog{
constructor(name:string){
this.name = name
}
name: string
run(){}
}
console.log(Dog.prototype)
let dog = new Dog("wangcai")
console.log(dog)
类的继承
1
2
3
4
5
6
7
class Husky extends Dog{
constructor (name:string,color:string){
super(name)
this.color = color;
}
color:string
}

类的属性 有三种 public private protect

记得以前学java 阿伟讲的很清楚。

public 谁都能进

private 只有类成员 才能进

protect 就是名义上是这个家的成员也能进 受法律保护。外面的孩子。只能在类或者子类中使用,不能在实例中使用

抽象类和多态
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
abstract class Animal{
eat(){
console.log('eat')
}
abstract sleep():void
}
class Dog extends Animal{
constructoi(name:string){
super()
this.name = name
}
name: string
run() {}
sleep(){
console.log('dog is sleeping')
}
}
let dog = new Dog('wangcai')
dog.sleep();


Class Cat extends Animal{
sleep(){
console.log('Cat is sleeping's)
}
}

let cat = new Cat();

let animals: Animal[] = [dog,cat]
animals.forEach(i => {
i.sleep()
})


class WorkFlow{
step1(){
return this;
}
step2(){
return this;
}
}

new WorkFlow().step1().step2()

class Myglow extends WorkFlow{
next(){
return this;
}
}
new Myflow().next().stpe1().next().step2()

类和接口之间的关系

类可以实现接口

接口之间可以互相继承

接口继承类 such as

1
2
3
4
5
6
7
8
9
10
11
12
13
class Auto{
state = 1
}
interface AutoInterface extends Auto{

}
class C inplements AutoInterface{
state = 1
}

class Bus extends Auto inplements AutoInterface{

}

image-20231105100428773

泛型

不预先确定的数据类型,具体的类型在使用的时候 才能确定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function log<T>(value:T):T{
console.log(value);
return value;
}

log<string[]>(['a','b'])
log(['a','b'])

type Log = <T>(value:T) => T
<T> 表示这个函数类型是一个泛型函数类型,其中 T 是一个类型参数

interface Log<T>{
{value: T}:T
}
// 实现的时候 需要定义一个类型
let myLog: Log<number> = log
myLog(1)

// 也可以指定默认的类型
interface Log<T = string>{
(value:T):T
}
泛型类与泛型约束
1
2
3
4
5
6
7
8
9
interface Length {
length: number
}
function log<T extend Length>(value:T):T{
console.log(value,value.length)
return value
}
log([1])
log('123')

image-20231105101953120

类型检查机制

类型推断

不需要指定变量的类型(函数的返回值类型),typescript可以根据某些规则自动地为其推断出一个类型

  • 基础类型推断
  • 最佳通用类型推断
  • 上下文类型推断

类型断言可以自己指定属性,不需要他来推断

1
2
3
4
5
6
7
8
9
10
interface Foo{
bar: number
}
let foo = {} as Foo
for.bar = 1

//尽量还是自己声明类型
let foo: Foo = {
bar : 1
}

类型兼容性

当一个类型Y 可以被赋值给另一个类型X时,我们就可以说类型X兼容类型Y

X兼容Y: X(目标类型) = Y(源类型)

关闭tsconfig.json 中的‘strictNullChecks’

string s 可以被赋值给null

字符型可以兼容null类型。

源类型如果包括一个类型的所有类型,成员少的可以兼容成员多的。

对象类型 和 接口类型不一样?

对象: 成员对象多的可以兼容成员少的 跟接口 相反。

? 没懂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface Point3D{
x:number;
y:number;
z:number;
}
interface Point2D{
x:number;
y:number;
}
let p3d = (point:Point3D) => {};
let p2d = (point:Point2D) => {};
为什么 Point2D = point3D
而 p2d = p3d 则不行


// 这是因为在 TypeScript 中,接口(Interface)和函数的类型检查机制是不同的。


对于接口,TypeScript 使用的是结构性类型系统。在这种类型系统中,如果 TypeATypeB 的子类型,那么 TypeA 的对象可以赋值给 TypeB 的对象。在你的例子中,Point3D 接口包含了 Point2D 接口的所有属性,因此 Point3D 可以看作是 Point2D 的子类型,所以 Point3D 类型的对象可以赋值给 Point2D 类型的对象²。

然而,对于函数,TypeScript 使用的是名义类型系统。在这种类型系统中,函数的参数类型必须完全匹配。也就是说,如果一个函数的参数类型是 TypeA,那么我们只能将类型为 TypeA 的对象传递给这个函数,不能将 TypeA 的子类型的对象传递给这个函数¹。在你的例子中,函数 p3dp2d 的参数类型分别是 Point3DPoint2D,尽管 Point3DPoint2D 的子类型,但你不能将 p3d 赋值给 p2d,因为 p3d 接受的参数类型是 Point3D,而 p2d 接受的参数类型是 Point2D,这两者并不完全匹配¹。 */

在 TypeScript 中,你可以将 p2d 赋值给 p3d,因为 p2d 函数接受的参数类型 Point2Dp3d 函数参数类型 Point3D 的超类型。换句话说,任何 Point2D 类型的对象都是 Point3D 类型的对象,因此 p2d 函数可以接受任何 p3d 函数可以接受的参数。

这是因为在 TypeScript 的类型系统中,函数的兼容性是基于参数列表和返回类型的。对于参数列表,TypeScript 采用 “鸭子类型”(Duck Typing)或 “结构子类型化”(Structural Subtyping)。在这种类型系统中,如果 TypeB 的每个属性都存在于 TypeA 中,那么 TypeB 就被认为是 TypeA 的子类型。因此,如果一个函数 f1 的每个参数都存在于另一个函数 f2 的参数列表中,那么 f1 就可以被赋值给 f2

所以,你可以将 p2d 赋值给 p3d,但不能将 p3d 赋值给 p2d,因为 p3d 的参数类型 Point3D 包含了 p2d 的参数类型 Point2D 中没有的属性 z

有待商榷 上面 后面在寻找资料。

枚举兼容性

enum Fruit{Apple,Banana}

enum Color{Red,Yellow}

let fruit:Fruit.Apple = 3

let no: number = Fruit.Apple

let color: Color.Red = Fruit.App 这是不行的

// 类的兼容性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class A{
constructor(p:number,q:number){}
id: numner = 1
private name: string =''
}
class B{
static s = 1
constructor(p:number){}
id:number = 2
private name: string= ''
}
//如果没有私有成员 创建的两个实例是兼容的/*
/因为 都具有一个实例属性id 构造函数和静态成员是不做比较的。*/
有私有成员 父类和子类是互相兼容的
泛型兼容

只有在泛型给成员变量使用时才会影响兼容性

类型保护

交叉类型和联合类型


未完待续