ts学习
- 目录
- 概述
- 需求:
- 设计思路
- 实现思路分析
- 1.TypeScript 基础类型
- 2.TypeScript 变量声明
- 3.TypeScript 接口
- 4.TypeScript 类
- 5.TypeScript 函数
- 5.TypeScript 泛型
- 5.TypeScript 枚举
- TypeScript 类型推论
- TypeScript 类型兼容性
- TypeScript 高级类型
- TypeScript 迭代器和生成器
- TypeScript 模块
- TypeScript 命名空间
- TypeScript 模块解析
- TypeScript 声明合并
- TypeScript 装饰器
- TypeScript 三斜线指令
- 参考资料和推荐阅读
Survive by day and develop by night.
talk for import biz , show your perfect code,full busy,skip hardness,make a better result,wait for change,challenge Survive.
happy for hardess to solve denpendies.
目录
概述
需求:
设计思路
实现思路分析
1.TypeScript 基础类型
TypeScript 支持以下基础类型:
- boolean:布尔类型,表示真或假。
- number:数字类型,表示整数或浮点数。
- string:字符串类型,表示文本。
- Array:数组类型,表示由相同类型的元素组成的有序集合。
- Tuple:元组类型,表示由固定数量和类型的元素组成的数组。
- enum:枚举类型,表示一组具有命名值的常量。
- any:任意类型,表示可以赋给任意类型的值。
- void:空类型,表示没有任何值。
- null 和 undefined:表示不存在的值。
- never:表示永不返回的函数类型或抛出异常的函数类型。
此外,TypeScript 还支持联合类型、交叉类型、类型别名等高级类型。
2.TypeScript 变量声明
TypeScript 变量声明可以通过 let
、const
和 var
关键字来实现。
let
关键字用于声明一个块级作用域的变量,它的值可以被修改。const
关键字用于声明一个块级作用域的常量,它的值不能被修改。var
关键字用于声明一个函数作用域的变量,它的值可以被修改。但是,它有一些特殊的行为,比如在循环中使用var
声明的变量会被提升到循环的外部作用域。
以下是一些示例:
// 使用 let 声明变量
let name: string = "Alice";
name = "Bob"; // 可以修改
// 使用 const 声明常量
const age: number = 30;
age = 40; // 错误,常量的值不能修改
// 使用 var 声明变量
function foo() {
var x = 10;
if (true) {
var x = 20; // 在同一个函数作用域中,可以重新声明同名的变量
}
console.log(x); // 输出 20
}
foo();
总结:
- 推荐使用
let
和const
关键字来声明变量和常量,因为它们的作用域更清晰,同时也可以避免一些潜在的 bug。 - 避免使用
var
关键字,因为它的行为比较奇特,容易造成问题。
3.TypeScript 接口
TypeScript 接口是一种用于定义代码结构的抽象概念。它可以描述一个对象的属性和方法,以及函数的参数和返回值的类型。接口可以被类实现,表示类必须遵守接口定义的结构。接口也可以被对象直接使用,用于限制对象的结构。
接口的定义使用关键字interface
,后面跟着接口的名称和属性、方法的定义。例如:
interface Person {
name: string;
age: number;
sayHello: () => void;
}
上面的代码定义了一个名为Person
的接口,其中有name
和age
属性,以及sayHello
方法。可以通过实现这个接口来创建一个符合Person
接口定义的类或对象。
在接口中,属性可以有不同的可选性,使用?
表示可选属性。接口中的方法可以有不同的可选性和参数个数。
接口还支持继承,使用extends
关键字来实现继承。例如:
interface Student extends Person {
grade: number;
}
上面的代码定义了一个名为Student
的接口,继承了Person
接口的属性和方法,并新增了一个grade
属性。
使用接口来定义代码结构可以提高代码的可读性和可维护性,同时还能在编译时进行类型检查,避免一些常见的错误。
4.TypeScript 类
TypeScript 是一种静态类型检查的 JavaScript 的超集,它可以在编写 JavaScript 代码的同时提供类型检查等功能。TypeScript 支持使用类来组织代码。
在 TypeScript 中,可以使用 class
关键字来声明一个类。类可以具有属性和方法。属性可以是公共的、私有的或受保护的,方法可以是公共的、私有的或受保护的。
下面是一个示例 TypeScript 类的代码:
class Person {
private name: string;
public age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
public sayHello() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}
}
const person = new Person("John", 30);
person.sayHello();
在上面的例子中,Person
类有一个私有属性 name
和一个公共属性 age
。它还有一个构造函数,用于初始化对象的属性。Person
类还有一个公共方法 sayHello
,用于打印对象的信息。
创建一个 Person
类的实例后,可以调用 sayHello
方法来打印对象的信息。
TypeScript 类提供了一种封装和组织代码的方式,并允许使用面向对象编程的概念,如继承和多态。。
5.TypeScript 函数
TypeScript 函数可以有以下几种形式:
- 命名函数:使用 function 关键字定义的函数,可以通过函数名调用。
function add(x: number, y: number): number {
return x + y;
}
let result = add(2, 3);
- 匿名函数:没有函数名的函数,可以将其赋值给一个变量,然后通过该变量调用。
let add = function(x: number, y: number): number {
return x + y;
};
let result = add(2, 3);
- 箭头函数:使用箭头 (=>) 语法定义的函数,可以简化函数的书写。箭头函数会自动捕获其所在的上下文中的 this 值。
let add = (x: number, y: number): number => {
return x + y;
};
let result = add(2, 3);
- 可选参数:在参数名称后面加上问号 (?),表示该参数为可选参数,可以省略。可选参数必须放在参数列表的最后。
function createUser(name: string, age?: number): void {
console.log(`Name: ${name}`);
if (age) {
console.log(`Age: ${age}`);
}
}
createUser("Alice"); // Name: Alice
createUser("Bob", 25); // Name: Bob, Age: 25
- 默认参数:为参数指定默认值,在参数名称后面使用等号 (=) 进行赋值。
function createUser(name: string, age: number = 18): void {
console.log(`Name: ${name}`);
console.log(`Age: ${age}`);
}
createUser("Alice"); // Name: Alice, Age: 18
createUser("Bob", 25); // Name: Bob, Age: 25
- 剩余参数:使用三个点 (…) 表示剩余参数,可以接收任意数量的参数,并将它们存储为一个数组。
function addNumbers(...numbers: number[]): number {
let sum = 0;
for (let num of numbers) {
sum += num;
}
return sum;
}
let result = addNumbers(1, 2, 3, 4, 5);
console.log(result); // 15
5.TypeScript 泛型
TypeScript 泛型允许我们在定义函数、类或接口的时候使用参数类型的占位符,然后在实际调用的时候再指定具体的类型。它的主要目的是增强代码的灵活性和可重用性。
在 TypeScript 中,我们可以使用 <T>
来表示一个泛型类型。泛型类型可以放在函数的参数、函数的返回值、类的属性和方法等各种位置。
例如,我们可以定义一个泛型函数来交换两个变量的值:
function swap<T>(a: T, b: T): void {
let temp: T = a;
a = b;
b = temp;
}
let a: number = 1;
let b: number = 2;
swap<number>(a, b);
console.log(a, b); // 输出 2 1
在上面的例子中,我们使用 <T>
来表示参数 a
和 b
的类型,然后在调用函数 swap
的时候指定具体的类型为 number
。
除了单个类型的泛型之外,还可以使用多个泛型类型。例如,我们可以定义一个泛型函数来创建一个数组:
function createArray<T>(length: number, value: T): T[] {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result.push(value);
}
return result;
}
let array1: number[] = createArray<number>(5, 0);
console.log(array1); // 输出 [0, 0, 0, 0, 0]
let array2: string[] = createArray<string>(3, 'hello');
console.log(array2); // 输出 ['hello', 'hello', 'hello']
在上面的例子中,我们使用 <T>
来表示数组元素的类型,并在调用函数 createArray
的时候指定具体的类型为 number
和 string
。
除了在函数中使用泛型之外,我们还可以在类和接口中使用泛型。例如,我们可以定义一个泛型类来表示一个队列的数据结构:
class Queue<T> {
private items: T[] = [];
enqueue(item: T): void {
this.items.push(item);
}
dequeue(): T | undefined {
return this.items.shift();
}
}
let queue: Queue<number> = new Queue<number>();
queue.enqueue(1);
queue.enqueue(2);
queue.enqueue(3);
console.log(queue.dequeue()); // 输出 1
console.log(queue.dequeue()); // 输出 2
console.log(queue.dequeue()); // 输出 3
在上面的例子中,我们使用 <T>
来表示队列中元素的类型,并在创建队列对象的时候指定具体的类型为 number
。
总之,TypeScript 泛型为我们提供了一种灵活和可重用的方式来处理不同类型的数据。通过使用泛型,我们可以编写更通用和可扩展的代码。
5.TypeScript 枚举
TypeScript 中的枚举(enum)是一种数据类型,用于定义一组具有名称和值的常量。枚举可以帮助我们在代码中使用更直观的方式表示一组相关的常量。
以下是 TypeScript 中定义和使用枚举的基本语法:
enum Color {
Red,
Green,
Blue
}
let myColor: Color = Color.Green;
在上面的代码中,我们定义了一个名为 Color
的枚举,并定义了三个常量值 Red
、Green
和 Blue
。然后,我们使用 Color
类型的变量 myColor
来保存枚举常量 Green
。
枚举的值默认从 0 开始,依次递增。因此,Color.Red
的值为 0,Color.Green
的值为 1,Color.Blue
的值为 2。如果需要指定具体的值,可以手动为枚举常量赋值:
enum Color {
Red = 100,
Green = 200,
Blue = 300
}
枚举的值可以是任意类型,不仅限于数字。例如,可以使用字符串类型作为枚举的值:
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT"
}
枚举在编译为 JavaScript 代码时会被转换为对象。因此,可以通过枚举的名称来访问枚举常量的值,也可以通过枚举常量的值来获取对应的枚举名称:
console.log(Color.Red); // 输出:0
console.log(Color[0]); // 输出:Red
总结一下,TypeScript 的枚举提供了一种简单、直观的方式来定义和使用一组常量,可以提高代码的可读性和维护性。
TypeScript 类型推论
TypeScript类型推论是指在编写代码时,TypeScript根据变量的初始化值和使用方式来推断其类型。换句话说,当我们声明一个变量并初始化时,TypeScript会根据我们提供的值来推断该变量的类型。例如:
let num = 10; // TypeScript会推断num的类型为number
let str = "Hello"; // TypeScript会推断str的类型为string
let bool = true; // TypeScript会推断bool的类型为boolean
TypeScript类型推论还可以根据变量的使用方式来推断其类型。例如:
function add(a, b) {
return a + b;
}
let result = add(10, 5); // TypeScript会推断result的类型为number
在这个例子中,TypeScript推断出a
和b
的类型为any
,因为它们没有明确的类型注解。然后,TypeScript根据add
函数的返回值推断出result
的类型为number
。
类型推论在很多情况下可以简化代码,不需要显式地指定变量的类型,而是让TypeScript根据上下文推断出类型。但在某些情况下,为了明确变量的类型,我们还是可以使用类型注解来指定变量的类型。
TypeScript 类型兼容性
TypeScript的类型兼容性是指在类型检查期间,如果一个类型A可以赋值给另一个类型B,那么就认为类型A兼容类型B。
TypeScript的类型兼容性规则如下:
-
普通类型的兼容性:如果A的类型是B的子类型,或者A和B是相同类型,那么A兼容B。
-
函数参数的兼容性:如果一个函数的参数类型是另一个函数的参数类型的子类型,那么这两个函数是兼容的。
-
可选参数和剩余参数的兼容性:可选参数可以兼容必选参数,而剩余参数可以兼容任意个数的参数。
-
函数返回值的兼容性:如果一个函数的返回值类型是另一个函数的返回值类型的父类型,那么这两个函数是兼容的。
-
数组的兼容性:如果一个数组的元素类型是另一个数组的元素类型的子类型,那么这两个数组是兼容的。
-
对象的兼容性:如果一个对象的属性和方法是另一个对象的子集,那么这两个对象是兼容的。
-
类的兼容性:如果一个类的属性和方法是另一个类的子集,那么这两个类是兼容的。
-
泛型的兼容性:如果一个泛型类型参数在一个类型中是协变的,那么这个类型参数可以兼容另一个类型。
通过类型兼容性,TypeScript可以进行更严格的类型检查,减少潜在的错误。
TypeScript 高级类型
TypeScript 是一种静态类型检查的 JavaScript 超集,它提供了一些高级类型特性,以帮助开发者更精确地描述和操作数据类型。下面是一些 TypeScript 的高级类型:
-
泛型(Generics):允许函数、类和接口在操作多种类型的数据时保持类型安全性。可以使用泛型来创建可重用的组件,而不必指定具体的数据类型。
-
联合类型(Union Types):允许变量具有多个可能的数据类型。可以使用联合类型来定义一个变量可以接受多种类型的值。
-
交叉类型(Intersection Types):允许将多个类型合并成一个新的类型。可以使用交叉类型来创建具有多个特征的对象或函数。
-
类型别名(Type Aliases):允许为复杂的类型定义一个简洁的别名。可以使用类型别名来提高代码的可读性和可维护性。
-
字面量类型(Literal Types):允许变量只接受特定的字面量值。可以使用字面量类型来限制变量的取值范围,并提供更严格的类型检查。
-
可辨识联合(Discriminated Unions):允许通过共同的属性来区分不同的联合类型成员。可以使用可辨识联合来创建更精确的类型判断和条件分支。
-
条件类型(Conditional Types):允许类型根据条件进行推断和操作。可以使用条件类型来实现复杂的类型转换和条件判断。
这些高级类型特性可以帮助开发者在编写 TypeScript 代码时更加灵活和类型安全。可以根据具体的需求来选择合适的高级类型,以提高代码的可读性、可维护性和性能。
TypeScript 迭代器和生成器
在TypeScript中,迭代器和生成器是用于处理集合中的元素的功能。迭代器是一个对象,它实现了next()方法并返回一个包含value和done属性的对象。生成器是一个特殊的函数,它使用关键字yield来定义可迭代的值。生成器函数内部可以使用yield语句来暂停函数的执行,并返回一个包含value和done属性的对象。
迭代器的使用示例:
class MyIterator {
private data: any[];
private index: number;
constructor(data: any[]) {
this.data = data;
this.index = 0;
}
next(): { value: any, done: boolean } {
if (this.index < this.data.length) {
return {
value: this.data[this.index++],
done: false
};
} else {
return {
value: undefined,
done: true
};
}
}
}
const myIterator = new MyIterator([1, 2, 3]);
console.log(myIterator.next()); // { value: 1, done: false }
console.log(myIterator.next()); // { value: 2, done: false }
console.log(myIterator.next()); // { value: 3, done: false }
console.log(myIterator.next()); // { value: undefined, done: true }
生成器的使用示例:
function* myGenerator() {
yield 1;
yield 2;
yield 3;
}
const iterator = myGenerator();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
使用迭代器和生成器可以更方便地处理集合中的元素,可以在循环中使用for…of语句来遍历生成器的值,而不需要使用while循环和next()方法。
TypeScript 模块
TypeScript 模块是一种组织和管理代码的方式。模块可以将代码分隔成独立的文件或文件夹,并且可以按需引入和导出模块的功能。
在 TypeScript 中,使用模块可以实现以下目标:
- 将代码分离成独立的文件或文件夹,提高代码的可读性和可维护性。
- 隐藏内部实现细节,只暴露外部接口,提供更好的封装性。
- 实现代码的重用,可以在不同的项目中共享模块。
- 提供依赖管理和版本控制的能力。
在 TypeScript 中,可以使用两种模块系统:CommonJS 和 ES6 模块。CommonJS 模块是 Node.js 中使用的模块系统,而 ES6 模块则是标准的 JavaScript 模块系统。
使用 CommonJS 模块时,可以使用 require()
函数来引入模块,使用 module.exports
来导出模块的功能:
// moduleA.ts
function sum(a: number, b: number): number {
return a + b;
}
module.exports = sum;
// main.ts
const sum = require('./moduleA');
console.log(sum(1, 2)); // 输出: 3
使用 ES6 模块时,可以使用 import
和 export
关键字来导入和导出模块的功能:
// moduleA.ts
export function sum(a: number, b: number): number {
return a + b;
}
// main.ts
import { sum } from './moduleA';
console.log(sum(1, 2)); // 输出: 3
除了这两种模块系统,TypeScript 还支持其他一些模块系统,如 AMD 和 UMD 模块系统。可以根据具体的项目需求选择适合的模块系统。
TypeScript 命名空间
在 TypeScript 中,命名空间是一种用来封装和组织代码的方式。
命名空间可以看作是一个容器,用来包含一组相关的类、接口、函数和枚举等。通过将相关代码放在同一个命名空间中,可以避免命名冲突,并且更好地组织和管理代码。
在 TypeScript 中,使用关键字 namespace
来声明一个命名空间。命名空间中可以包含多个模块(module),每个模块可以包含多个类、接口、函数等。
以下是一个使用命名空间的示例:
namespace MyNamespace {
export class MyClass {
// class implementation
}
export function myFunction() {
// function implementation
}
}
在上面的示例中,MyNamespace
是一个命名空间,包含了一个类 MyClass
和一个函数 myFunction
。通过使用 export
关键字,可以将这些内容暴露给外部使用。
使用命名空间的时候,可以使用点运算符来访问命名空间中的成员。例如:
const myObject = new MyNamespace.MyClass();
MyNamespace.myFunction();
另外,可以使用 import
语句来导入一个命名空间中的成员,这样就可以直接使用成员,而不需要使用命名空间的前缀。
import { MyClass, myFunction } from "MyNamespace";
const myObject = new MyClass();
myFunction();
需要注意的是,命名空间和模块(module)是不同的概念。命名空间主要用于在编译时进行代码的组织和封装,而模块则主要用于在运行时进行代码的组织和加载。在实际项目中,推荐使用模块来组织和管理代码,而不是过度依赖命名空间。
TypeScript 模块解析
TypeScript 的模块解析是指在编译阶段,将导入的模块转换为可执行的 JavaScript 代码的过程。TypeScript 支持两种模块解析策略:经典解析和 Node.js 解析。
-
经典解析:
- 相对路径导入:从当前文件所在目录开始,按照相对路径逐级向上查找目标模块。
- 非相对路径导入:从配置文件中指定的路径开始,按照配置的路径逐级向上查找目标模块。
- 配置文件:可以通过 tsconfig.json 文件来配置模块的解析策略。
-
Node.js 解析:
- 相对路径导入:从当前文件所在目录开始,按照相对路径逐级向上查找目标模块。
- 非相对路径导入:首先从当前文件所在目录开始,按照相对路径逐级向上查找目标模块。如果找不到,则尝试从当前文件所在目录下的 node_modules 目录中查找目标模块。如果还找不到,则从上一级目录开始重复上述步骤,直到找到目标模块或者到达文件系统的根目录。
- 全局模块解析:TypeScript 可以通过配置文件中的 paths 或 baseUrl 字段来指定全局模块的解析路径。
在模块解析的过程中,TypeScript 还会根据目标模块的文件类型进行相应的处理:
- .ts 和 .tsx:直接编译为 JavaScript 代码。
- .d.ts:将其视为声明文件,不进行编译。
- .js 和 .jsx:只有当 allowJs 或者 checkJs 选项被设置为 true 时,TypeScript 才会对其进行编译。
总之,TypeScript 的模块解析策略非常灵活,可以根据项目的需求选择适合的解析方式。
TypeScript 声明合并
TypeScript 中的声明合并是指将多个同名的声明合并为一个声明。这可以用于合并接口、类型别名、命名空间和类等。
接口合并示例:
interface A {
x: number;
}
interface A {
y: number;
}
const a: A = {
x: 1,
y: 2
};
合并后的接口 A
相当于:
interface A {
x: number;
y: number;
}
类型别名合并示例:
type B = {
x: number;
};
type B = {
y: number;
};
const b: B = {
x: 1,
y: 2
};
合并后的类型别名 B
相当于:
type B = {
x: number;
y: number;
};
命名空间合并示例:
namespace C {
export const x = 1;
}
namespace C {
export const y = 2;
}
console.log(C.x, C.y); // 输出 1 2
类合并示例:
class D {
x = 1;
}
class D {
y = 2;
}
const d = new D();
console.log(d.x, d.y); // 输出 1 2
在声明合并时,如果存在重复的属性或方法,TypeScript 会将它们的类型合并为联合类型。如果是函数重载,则会将多个函数定义合并为一个函数定义,其中包含多个重载签名。
TypeScript 装饰器
TypeScript 装饰器是一种特殊的注释,用于修改类、方法、属性或参数的行为。装饰器提供了一种简洁的方式来给现有的代码添加新的功能。
装饰器可以应用于类、方法、属性和参数。通过在这些目标上添加装饰器,可以修改它们的行为。
装饰器使用 @ 符号来标记,后面跟着一个函数。这个函数在运行时被调用,并可以用来修改目标的行为。
例如,下面的代码演示了如何使用装饰器来修改一个类的行为:
function log(target: Function) {
console.log(`Logging from ${target.name}`);
}
@log
class MyClass {
// class implementation
}
在这个例子中,log 装饰器被应用在 MyClass 上。当 MyClass 被定义时,log 装饰器的函数会被调用,打印出 “Logging from MyClass”。
除了类装饰器,还有方法装饰器、属性装饰器和参数装饰器。方法装饰器可以修改方法的行为,属性装饰器可以修改属性的行为,参数装饰器可以修改方法参数的行为。
装饰器可以被链式调用,也可以传递参数。例如:
function log(target: Function) {
console.log(`Logging from ${target.name}`);
}
function debug(enabled: boolean) {
return function(target: Function) {
console.log(`Debugging ${enabled ? 'enabled' : 'disabled'}`);
}
}
@log
@debug(true)
class MyClass {
// class implementation
}
在这个例子中,debug 装饰器可以接收一个布尔值参数来决定是否启用调试。当 MyClass 被定义时,先应用 debug 装饰器,然后再应用 log 装饰器。
总的来说,TypeScript 装饰器是一种非常强大的工具,可以帮助开发人员修改现有代码的行为。它提供了一种简洁的方式来添加新的功能,使代码更易于扩展和维护。
TypeScript 三斜线指令
TypeScript 中的三斜线指令是一种特殊的注释标记,用于告诉编译器执行一些额外的操作。它们以 ///
开始,通常出现在文件的顶部。
常用的三斜线指令有三种:
-
/// <reference path="..."/>
:用于引用其他的 TypeScript 文件。当一个文件依赖于另一个文件时,可以使用该指令将它们连接起来。编译器会根据指令的顺序将文件进行编译。 -
/// <reference types="..."/>
:用于引用类型声明文件。当使用第三方库时,可以通过该指令引用对应的类型声明文件,以获得代码补全和类型检查的支持。 -
/// <amd-module name="..."/>
:用于指定模块的名称。该指令用于在 AMD 模块中指定模块的名称,以便在模块加载时使用。它一般与import
或export
语句一起使用。
三斜线指令是一种过时的写法,新版的 TypeScript 推荐使用模块导入语法和配置文件(如 tsconfig.json)来管理依赖和编译选项。因此,在新的项目中,通常不再使用三斜线指令。
参考资料和推荐阅读
参考资料
官方文档
开源社区
博客文章
书籍推荐
1.暂无
欢迎阅读,各位老铁,如果对你有帮助,点个赞加个关注呗!同时,期望各位大佬的批评指正~