PS:对前端技术感兴趣的朋友们,可以关注下我的《致力于前端的技术博客》哦!如果对你有帮助,欢迎赠个⭐️,会常更新内容,敬请期待!❤️❤️
基本配置(自动编译)
vscode 配置自动编译 ts 代码
- 运行 tsc --init,生成配置文件,改 outDir
- 运行任务,选择监视 tsconfig
ts数据类型
基本常用类型
布尔、数字、字符串
let flag: boolean = true;
let num: number = 123;
let str: string = "hello";
数组
let arr1: number[] = [1, 2, 3];
let arr2: any[] = [1, "ji", 3];
let arr3: Array<number> = [1, 2, 3];
元祖(属于数组的一种)
let tuple: [number, string] = [123, "hello"];
枚举
enum Status {
success = 1,
error = 2
}
enum Color {
blue,
red,
white
} //如果未赋值,结果打印下标
//let s: Status = Status.success
//console.log(s);
对象
declare function create(o: object | null): void;
create({ prop: 0 });
特殊类型
任意类型any
let random: any = 123;
//null和undefined 其他类型的子类型
let num1: number | undefined; //定义未赋值就是undefined
let num2: number | null | undefined;
null和undefined
let num: number;
let str: string;
// 这些类型能被赋予
num = null;
str = undefined;
空类型void(方法没有返回值)
function run1(): void {
console.log("void");
}
function run2(): number {
return 1;
}
never 类型(表示从来不会出现的值,基本用不到)
一个从来不会有返回值的函数(如:如果函数内含有 while(true) {}); 一个总是会抛出错误的函数(如:function foo() { throw new Error('Not Implemented') },foo 的返回类型是 never);
function error(message: string): never {
throw new Error(message);
}
//使用场景(详细检查)
function foo(x: string | number): boolean {
if (typeof x === 'string') {
return true;
} else if (typeof x === 'number') {
return false;
}
// 如果不是一个 never 类型,这会报错:
// - 不是所有条件都有返回值 (严格模式下)
// - 或者检查到无法访问的代码
// 但是由于 TypeScript 理解 `fail` 函数返回为 `never` 类型
// 它可以让你调用它,因为你可能会在运行时用它来做安全或者详细的检查。
return fail('Unexhaustive');
}
function fail(message: string): never {
throw new Error(message);
}
never 表示一个从来不会优雅的返回的函数时,你可能马上就会想到与此类似的 void,然而实际上,void 表示没有任何类型,never 表示永远不存在的值的类型。
泛型(在计算机科学中,许多算法和数据结构并不会依赖于对象的实际类型。然而,你仍然会想在每个变量里强制提供约束)
function reverse<T>(items: T[]): T[] {
const toreturn = [];
for (let i = items.length - 1; i >= 0; i--) {
toreturn.push(items[i]);
}
return toreturn;
}
联合类型(在 JavaScript 中,你希望属性为多种类型之一,如字符串或者数组。这就是联合类型所能派上用场的地方)
function formatCommandline(command: string[] | string) {
let line = '';
if (typeof command === 'string') {
line = command.trim();
} else {
line = command.join(' ').trim();
}
// Do stuff with line: string
}
交叉类型(在 JavaScript 中, extend 是一种非常常见的模式,在这种模式中,你可以从两个对象中创建一个新对象,新对象会拥有着两个对象所有的功能。)
function extend<T, U>(first: T, second: U): T & U {
const result = <T & U>{};
for (let id in first) {
(<T>result)[id] = first[id];
}
for (let id in second) {
if (!result.hasOwnProperty(id)) {
(<U>result)[id] = second[id];
}
}
return result;
}
const x = extend({ a: 'hello' }, { b: 42 });
// 现在 x 拥有了 a 属性与 b 属性
const a = x.a;
const b = x.b;
【tips】
- 如果你需要使用类型注解的层次结构,请使用接口。
- 它能使用 implements 和 extends 为一个简单的对象类型(像例子中的Coordinates)使用类型别名,仅仅有一个语义化的作用。与此相似,当你想给一个联合类型和交叉类型使用一个语意化的名称时,一个类型别名将会是一个好的选择。
ts函数
函数声明
function fn1(): string {
return "run";
}
let fn2 = function(): string {
return "run2";
};
方法传参
function getInfo1(name: string, age: number): string {
return `${name}---${age}`;
}
let getInfo2 = function(name: string, age: number): string {
return `${name}---${age}`;
};
可选参数
function get(name: string, age?: number): string {
if (age) {
return `${name}---${age}`;
} else {
return `${name}---年龄保密`;
}
}
默认参数
function get1(name: string, age: number = 20): string {
return `${name}---${age}`;
}
剩余参数
function sum(a: number, b: number, c: number): number {
return a + b + c;
}
// 三点运算符接收形参
function sum1(...result: number[]): number {
let sum = 0;
for (let i = 0; i < result.length; i++) {
sum += result[i];
}
return sum;
}
function sum2(initNum: number, ...result: number[]): number {
let sum = initNum;
for (let i = 0; i < result.length; i++) {
sum += result[i];
}
return sum;
}
函数重载
es5 中同名方法会存在替换
function info1(name: string): string;
function info1(age: number): number;
function info1(str: any): any {
if (typeof str === "string") {
return `我叫${str}`;
} else {
return `我的年龄是${str}`;
}
}
//console.log(info1('Amy'));
//console.log(info1(12));
function info2(name: string): string;
function info2(name: string, age: number): string;
function info2(name: any, age?: any): any {
if (age) {
return `我叫${name},我的年龄是${age}`;
} else {
return `我叫${name}`;
}
}
// 重载
function padding(all: number);
function padding(topAndBottom: number, leftAndRight: number);
function padding(top: number, right: number, bottom: number, left: number);
// Actual implementation that is a true representation of all the cases the function body needs to handle
function padding(a: number, b?: number, c?: number, d?: number) {
if (b === undefined && c === undefined && d === undefined) {
b = c = d = a;
} else if (c === undefined && d === undefined) {
c = a;
d = b;
}
return {
top: a,
right: b,
bottom: c,
left: d
};
}
interface Overloaded {
(foo: string): string;
(foo: number): number;
}
// 实现接口的一个例子:
function stringOrNumber(foo: number): number;
function stringOrNumber(foo: string): string;
function stringOrNumber(foo: any): any {
if (typeof foo === 'number') {
return foo * foo;
} else if (typeof foo === 'string') {
return `hello ${foo}`;
}
}
const overloaded: Overloaded = stringOrNumber;
// 使用
const str = overloaded(''); // str 被推断为 'string'
const num = overloaded(123); // num 被推断为 'number'
TypeScript 中的函数重载没有任何运行时开销。它只允许你记录希望调用函数的方式,并且编译器会检查其余代码。
ts中的类
es5 的继承(原型链+对象冒充实现继承)
function Person(name, age) {
this.name = name;
this.age = age;
}
function Student() {
Person.call(this, name, age);
//对象冒充(只能继承构造函数中的属性和方法,可以向父类传参)
}
Student.prototype = new Person();
//原型链继承(既可以继承构造函数中的属性和方法,也可以继承原型链上的属性和方法,但是实例化时无法向父类传参)
Student.prototype = Person.prototype; //另一种写法
定义类
class Person {
name: string; //省略了public关键字
constructor(name: string) {
this.name = name;
}
run(): void {
console.log(this.name);
}
getName(): string {
return this.name;
}
setName(name: string): void {
this.name = name;
}
}
实现继承 extends super
class Student extends Person {
constructor(name: string) {
super(name)
}
**子类扩展方法,子类也可以覆盖父类的方法
work(): void {
alert('在运动')
}
}
类修饰符
- public:在类内部、子类、和外部均可以访问
- protected:在类内部和子类可以访问,外部无法访问
- private:私有,只有类内部可以访问
属性如果不加修饰符,默认是 public
静态属性&&静态方法
应用举例(jquery)
function $(element) {
return new Base(element);
}
//静态方法
$.get = function() {};
function Base(element) {
this.element = element;
this.css = function(attr, value) {
this.element.style.attr = value;
};
}
class Person1 {
name: string; //省略了public关键字
constructor(name: string) {
this.name = name;
} //静态属性
static sex = "男"; //静态方法,无法直接调用类的属性,只能调用静态属性
static print(): void {
alert("静态方法");
} //实例方法
getName(): string {
return this.name;
}
setName(name: string): void {
this.name = name;
}
}
多态
多态是指父类定义方法不去实现,让继承它的子类去实现,每个子类有不同的表现
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
eat() {
console.log("吃的方法");
}
}
class Dog extends Animal {
constructor(name: string) {
super(name);
}
eat() {
return this.name + "吃肉";
}
}
class Cat extends Animal {
constructor(name: string) {
super(name);
}
eat() {
return this.name + "吃鱼";
}
}
抽象类
抽象类和抽象方法用来定义标准,要求子类必须实现抽象类中的所有方法,abstract 关键字,无法创造抽象类的实例
abstract class Animal1 {
public name: string;
constructor(name: string) {
this.name = name;
}
abstract eat(): any;
}
class Dog1 extends Animal1 {
constructor(name: any) {
super(name);
}
eat() {
console.log(`${this.name}吃肉`);
}
}
typescript 接口
属性接口
//对传入地值进行约束
function print_label1(label: string): void {
console.log(label);
}
function print_label2(labelInfo: { label: string }): void {
console.log(labelInfo);
}
//接口(批量方法约束)
interface FullName {
firstname: string;
lastname: string; //可选属性 lastname?: string;
}
//传到的参数必须包含firstname、lastname
function printName(name: FullName) {
console.log(`${name.firstname}---${name.lastname}`);
}
函数类型接口
interface encrypt {
(key: string, value: string): string;
}
let md5: encrypt = function(key: string, value: string) {
return key + value;
};
let sha1: encrypt = function(key: string, value: string) {
return key + "---" + value;
};
可索引接口
interface UserArr {
[index: number]: string;
}
let arr: UserArr = ["1", "2", "3"];
interface UserObj {
[index: string]: string;
}
let obj: UserObj = { name: "zhangsan", age: "12" };
类类型接口
interface AnimalI {
name: string;
eat(str: string): void;
}
class a1 implements AnimalI {
name: string;
constructor(name: string) {
this.name = name;
}
eat(food: string) {
console.log(`我是${this.name},我吃${food}`);
}
}
接口扩展
接口集成接口,类实现接口时需要实现所有的方法
interface Ani {
eat(): void;
}
//接口也可以相互继承
interface Per extends Ani {
work(): void;
}
class Programmer {
name: string;
constructor(name: string) {
this.name = name;
}
coding(code: string) {
console.log(this.name + code);
}
}
//集成父类+实现接口
class Web1 extends Programmer implements Per {
constructor(name: string) {
super(name);
}
eat() {}
work() {}
}
ts泛型
泛型函数
//泛型就是解决类\接口\方法的复用性 以及对不特定数据类型的支持
function getData(value: string): string {
return value;
}
//如果要传入什么类型,返回什么类型,就要使用到泛型
function getData1<T>(value: T): T {
return value;
}
getData1<number>(123);
getData1<string>("abc");
泛型类
//(原始)
class MinClass {
list: number[] = [];
add(value: number): void {
this.list.push(value);
}
min(): number {
let min = this.list[0];
for (let i = 0; i < this.list.length; i++) {
if (min > this.list[i]) {
min = this.list[i];
}
}
return min;
}
}
//(泛型)
class MinClass1<T> {
public list: T[] = [];
add(value: T): void {
this.list.push(value);
}
min(): T {
let min = this.list[0];
for (let i = 0; i < this.list.length; i++) {
if (min > this.list[i]) {
min = this.list[i];
}
}
return min;
}
}
let m1 = new MinClass1<number>();
m1.add(1);
m1.add(2);
m1.add(3);
alert(m1.min());
let m2 = new MinClass1<string>();
m2.add("a");
m2.add("b");
m2.add("c");
alert(m2.min());
泛型接口
//(普通函数类型接口)
interface ConfigFn0 {
(value1: string, value2: string): string;
}
let setData0: ConfigFn0 = function(value1: string, value2: string) {
return value1 + value2;
};
//泛型接口(写法1)
interface ConfigFn1 {
<T>(value1: T): T;
}
let setData1: ConfigFn1 = function<T>(value: T): T {
return value;
};
setData1 < string > "abdc";
//泛型接口(写法2)
interface ConfigFn2<T> {
(value1: T): T;
}
function myGetData<T>(value: T): T {
return value;
}
let myFn: ConfigFn2<string> = myGetData;
其他补充
枚举类型
enum CardSuit {
Clubs,
Diamonds,
Hearts,
Spades
}
// 简单的使用枚举类型
let Card = CardSuit.Clubs;
// 类型安全
Card = 'not a member of card suit'; // Error: string 不能赋值给 `CardSuit` 类型
元组类型
let nameNumber: [string, number];
// Ok
nameNumber = ["Jenny", 221345];
// Error
nameNumber = ["Jenny", "221345"];
// 将其与 TypeScript 中的解构一起使用:
const [name, num] = nameNumber;
类型别名
type StrOrNum = string | number;
// 使用
let sample: StrOrNum;
sample = 123;
sample = "123";
// 会检查类型
sample = true; // Error
type Text = string | { text: string };
type Coordinates = [number, number];
type Callback = (data: string) => void;
tips 如果你需要使用类型注解的层次结构,请使用接口。它能使用 implements 和 extends 为一个简单的对象类型(像例子中的 Coordinates)使用类型别名,仅仅有一个语义化的作用。与此相似,当你想给一个联合类型和交叉类型使用一个语意化的名称时,一个类型别名将会是一个好的选择。
类型断言
const foo = {};
foo.bar = 123; // Error: 'bar' 属性不存在于 ‘{}’
foo.bas = "hello"; // Error: 'bas' 属性不存在于 '{}'
// 这里的代码发出了错误警告,因为 foo 的类型推断为 {},即是具有零属性的对象。因此,你不能在它的属性上添加 bar 或 bas
interface Foo {
bar: number;
bas: string;
}
const foo = {} as Foo;
foo.bar = 123;
foo.bas = "hello";
双重断言
function handler(event: Event) {
const element = event as HTMLElement; // Error: 'Event' 和 'HTMLElement' 中的任何一个都不能赋值给另外一个
}
//如果你仍然想使用那个类型,你可以使用双重断言。首先断言成兼容所有类型的 any,编译器将不会报错:
function handler(event: Event) {
const element = (event as any) as HTMLElement; // ok
}
类型保护
function doSome(x: number | string) {
if (typeof x === "string") {
// 在这个块中,TypeScript 知道 `x` 的类型必须是 `string`
console.log(x.subtr(1)); // Error: 'subtr' 方法并没有存在于 `string` 上
console.log(x.substr(1)); // ok
}
x.substr(1); // Error: 无法保证 `x` 是 `string` 类型
}
function doStuff(arg: Foo | Bar) {
if (arg instanceof Foo) {
console.log(arg.foo); // ok
console.log(arg.bar); // Error
}
if (arg instanceof Bar) {
console.log(arg.foo); // Error
console.log(arg.bar); // ok
}
}
function doStuff(arg: Foo | Bar) {
if (arg instanceof Foo) {
console.log(arg.foo); // ok
console.log(arg.bar); // Error
} else {
// 这个块中,一定是 'Bar'
console.log(arg.foo); // Error
console.log(arg.bar); // ok
}
}
function doStuff(q: A | B) {
if ("x" in q) {
// q: A
} else {
// q: B
}
}
function doStuff(arg: Foo | Bar) {
if (arg.kind === "foo") {
console.log(arg.foo); // ok
console.log(arg.bar); // Error
} else {
// 一定是 Bar
console.log(arg.foo); // Error
console.log(arg.bar); // ok
}
}
自变量类型
type CardinalDirection = "North" | "East" | "South" | "West";
function move(distance: number, direction: CardinalDirection) {
// ...
}
move(1, "North"); // ok
move(1, "Nurth"); // Error
type OneToFive = 1 | 2 | 3 | 4 | 5;
type Bools = true | false;
使用用例
TypeScript 枚举类型是基于数字的,你可以使用带字符串字面量的联合类型,来模拟一个基于字符串的枚举类型,就好像上文中提出的 CardinalDirection。你甚至可以使用下面的函数来生成 key: value 的结构:
// 用于创建字符串列表映射至 K: V 的函数
// 用于创建字符串列表映射至 `K: V` 的函数
function strEnum<T extends string>(o: Array<T>): { [K in T]: K } {
return o.reduce((res, key) => {
res[key] = key;
return res;
}, Object.create(null));
}
// 创建 K: V
const Direction = strEnum(["North", "South", "East", "West"]);
// 创建一个类型
type Direction = keyof typeof Direction;
// 简单的使用
let sample: Direction;
sample = Direction.North; // Okay
sample = "North"; // Okay
sample = "AnythingElse"; // ERROR!
readonly
function foo(config: { readonly bar: number; readonly bas: number }) {
// ..
}
type Foo = {
readonly bar: number;
readonly bas: number;
};
class Foo {
readonly bar = 1; // OK
readonly baz: string;
constructor() {
this.baz = "hello"; // OK
}
}
这有一个 Readonly 的映射类型,它接收一个泛型 T,用来把它的所有属性标记为只读类型:
type Foo = {
bar: number;
bas: number;
};
type FooReadonly = Readonly<Foo>;
const foo: Foo = { bar: 123, bas: 456 };
const fooReadonly: FooReadonly = { bar: 123, bas: 456 };
foo.bar = 456; // ok
fooReadonly.bar = 456; // Error: bar 属性只读
设置为绝对的而不可变
你甚至可以把索引签名标记为只读:
interface Foo {
readonly [x: number]: number;
}
// 使用
const foo: Foo = { 0: 123, 2: 345 };
console.log(foo[0]); // ok(读取)
foo[0] = 456; // Error: 属性只读
自动推断
在一些情况下,编译器能把一些特定的属性推断为 readonly,例如在一个 class 中,如果你有一个只含有 getter 但是没有 setter 的属性,他能被推断为只读:
class Person {
firstName: string = "John";
lastName: string = "Doe";
get fullName() {
return this.firstName + this.lastName;
}
}
const person = new Person();
console.log(person.fullName); // John Doe
person.fullName = "Dear Reader"; // Error, fullName 只读
readonly 与 const 的不同点 const 用于变量;变量不能重新赋值给其他任何事物。 readonly 用于属性;用于别名,可以修改属性;
使用泛型
例如当你想创建一个字符串的队列时,你将不得不再次修改相当大的代码。我们真正想要的一种方式是无论什么类型被推入队列,被推出的类型都与推入类型一样。当你使用泛型时,这会很容易:
// 创建一个泛型类
class Queue<T> {
private data: T[] = [];
push = (item: T) => this.data.push(item);
pop = (): T | undefined => this.data.shift();
}
// 简单的使用
const queue = new Queue<number>();
queue.push(0);
queue.push("1"); // Error:不能推入一个 `string`,只有 number 类型被允许
tip: 你可以随意调用泛型参数,当你使用简单的泛型时,泛型常用 T、U、V 表示。如果在你的参数里,不止拥有一个泛型,你应该使用一个更语义化名称,如 TKey 和 TValue (通常情况下,以 T 做为泛型前缀也在如 C++ 的其他语言里做为模版。)