TypeScript 入门自学笔记(一)

TypeScript 的特性
类型系统按照类型检查的时机分类,可以分为动态类型和静态类型。

类型系统
TypeScript 是静态类型
动态类型:是指在运行时才会进行类型检查,类型错误往往会导致运行时错误。JavaScript 是一门解释型语言,没有编译阶段,所以它是动态类型,以下代码在运行时才会报错:

// test.js
let foo = 1;
foo.split(’ ');

// TypeError: foo.split is not a function 运行时会报错(foo.split 不是一个函数)
静态类型:是指编译阶段就能确定每个变量的类型,类型错误往往会导致语法错误。TypeScript 在运行前需要先编译为 JavaScript,而在编译阶段就会进行类型检查,所以 TypeScript 是静态类型,以下代码在编译阶段就会报错:

// test.ts
let foo: number = 1;
foo.split(’ ');

// Property ‘split’ does not exist on type ‘number’. 编译时报错(数字没有 split 方法),无法通过编译
TypeScript 是弱类型
类型系统按照是否允许隐式类型转换分类,可以分为强类型和弱类型。

以下代码在 JS或 TS 中都可以正常运行,运行时数字 1 会被隐式类型转换为字符串 ‘1’,加号 + 被识别为字符串拼接,所以打印出结果是字符串 ‘11’。

console.log(1 + ‘1’); // 11
TS 是完全兼容 JS的,并不会修改 JS 运行时的特性,所以它们都是弱类型。虽然 TS 不限制加号两侧的类型,但是可以借助类型系统,以及 ESLint 代码检查,来限制加号两侧必须同为数字或同为字符串。会在一定程度上使得 TypeScript 向强类型更近一步了——当然,这种限制是可选的。

这样的类型系统体现了 TypeScript 的核心设计理念:在完整保留 JavaScript 运行时行为的基础上,通过引入静态类型系统来提高代码的可维护性,减少可能出现的 bug。

原始数据类型基本使用
布尔值、数值、字符串、null、undefined,为变量指定类型,且变量值需与类型一致

let flag: boolean = false
let num: number = 15
let str: string = ‘abc’
var a: null = null
var b: undefined = undefined

// 编译通过
使用构造函数创造的对象不是原始数据类型,事实上 new XXX() 返回的是一个 XXX对象:

let flag:boolean=new Boolean(false) // Type ‘Boolean’ is not assignable to type ‘boolean’.
let num: number = new Number(15) // Type ‘Number’ is not assignable to type ‘number’.
let str: string = new String(‘abc’) // Type ‘String’ is not assignable to type ‘string’.

// 编译通过
但是可以直接调用 XXX 也可以返回一个 xxx 类型:

let flag: boolean = Boolean(false)
let num : number = Number(15)
let str : string = String(‘abc’)

// 编译通过
数值
使用 number定义数值类型:

let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
let binaryLiteral: number = 0b1010; // ES6 中的二进制表示法
let octalLiteral: number = 0o744; // ES6 中的八进制表示法
let notANumber: number = NaN;
let infinityNumber: number = Infinity;

编译结果:

var decLiteral = 6;
var hexLiteral = 0xf00d;
var binaryLiteral = 10; // ES6 中的二进制表示法
var octalLiteral = 484; // ES6 中的八进制表示法
var notANumber = NaN;
var infinityNumber = Infinity;

ES6 中二进制和八进制数值表示法:分别用前缀0b|0B和0o|0O表示。会被编译为十进制数字。

字符串
使用string定义字符串类型:

let myName: string = ‘Echoyya’;
let str: string = Hello, my name is ${myName}.; // 模板字符串
编译结果:

var myName = ‘Echoyya’;
var str = "Hello, my name is " + myName + “.”; // 模板字符串
ES6 中模板字符串:增强版的字符串,用反引号(`) 标识 ${expr} 用来在模板字符串中嵌入表达式。

空值 及(与Null 和 Undefined的区别)
JavaScript 没有空值(Void)的概念,在 TS中,用 void 表示没有任何返回值的函数:

function alertName(): void {
alert(‘My name is Tom’);
}
然而声明一个 void 类型的变量没什么用,因为只能将其赋值为 undefined 和 null:

let unusable: void = undefined;
Null 和 Undefined

let u: undefined = undefined;
let n: null = null;
区别:undefined 和 null 是所有类型的子类型。也就是说 undefined 类型的变量,可以赋值给所有类型的变量,包括 void 类型:

let num: number = undefined;

let u: undefined;
let str: string = u;
let vo: void= u;

// 编译通过
而 void 类型的变量不能赋值给其他类型的变量,只能赋值给 void 类型:

let u: void;
let num: number = u; // Type ‘void’ is not assignable to type ‘number’.
let vo: void = u; // 编译通过

任意值
任意值(Any)用来表示允许赋值为任意类型。一个普通类型,在赋值过程中是不被允许改变类型的,any 类型,允许被赋值为任意类型。
let str: string = ‘abc’;
str = 123;

// Type ‘number’ is not assignable to type ‘string’.

// -----------------------------------------------------------------

let str: any = ‘abc’;
str = 123;

// 编译通过
任意值可以访问任意属性和方法:
let anyThing: any = ‘hello’;

anyThing.setName(‘Jerry’);
anyThing.setName(‘Jerry’).sayHello();
anyThing.myName.setFirstName(‘Cat’);

console.log(anyThing.myName);
console.log(anyThing.myName.firstName);

// 编译通过
未声明类型的变量:如果变量在声明时,未指定类型,那么会被识别为任意值类型:
let something;
something = ‘seven’;
something = 7;

// 等价于

let something: any;
something = ‘seven’;
something = 7;

类型推论
若未明确指定类型,那么 TS 会依照类型推论(Type Inference)规则推断出一个类型。
以下代码虽然没有指定类型,但编译时会报错:

let data = ‘seven’;
data = 7;

// Type ‘number’ is not assignable to type ‘string’.
事实上,它等价于:

let data: string = ‘seven’;
data = 7;

// Type ‘number’ is not assignable to type ‘string’.
TS会在未明确指定类型时推测出一个类型,这就是类型推论。如果定义时未赋值,不管之后是否赋值,都会被推断成 any 类型:

let data;
data = ‘seven’;
data = 7;

// 编译通过
联合类型
联合类型(Union Types)表示取值可以为多种类型中的一种,使用 | 分隔每个类型。

let data: string | number; // 允许 data 可以是 string 或 number 类型,但不能是其他类型
data = ‘seven’;
data = 7;

data = true;

// Type ‘boolean’ is not assignable to type ‘string | number’.
访问联合类型的属性或方法:当不确定一个联合类型的变量到底是哪个类型时,只能访问此联合类型中所有类型共有的属性或方法:

function getLength(something: string | number): number {
return something.length;
}

// length 不是 string 和 number 的共有属性,所以会报错
// Property ‘length’ does not exist on type ‘string | number’.
// Property ‘length’ does not exist on type ‘number’.
访问 string 和 number 的共有属性:

function getString(something: string | number): string {
return something.toString();
}
联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型:

let data: string | number;
data = ‘seven’;
console.log(data.length); // 5
data = 7;
console.log(data.length); // 编译时报错

// Property ‘length’ does not exist on type ‘number’.
line2:data 被推断为 string,访问length 属性不会报错。

line4:data 被推断为 number,访问length 属性报错。

对象的类型——接口
在 TS中,使用接口(Interfaces)来定义对象的类型。可用于对类的一部分行为进行抽象以外,也常用于对对象的形状(Shape)进行描述。

interface Person {
name: string;
age: number;
}

let p1: Person = {
name: ‘Tom’,
age: 25
};
定义一个接口 Person(接口一般首字母大写),定义一个变量 tom 类型是 Person。这样就约束了 tom 的形状必须和接口 Person 一致。

确定属性
确定属性:赋值时,定义的变量的形状必须与接口形状保持一致。变量的属性比接口少或多属性都是不允许的:

interface Person {
name: string;
age: number;
}

let p1: Person = { // 缺少 age 属性
name: ‘Tom’
};

// Type ‘{ name: string; }’ is not assignable to type ‘Person’.
// Property ‘age’ is missing in type ‘{ name: string; }’.

// -----------------------------------------------------------------

let p2 Person = { // 多余 gender 属性
name: ‘Tom’,
age: 25,
gender: ‘male’
};

// Type ‘{ name: string; age: number; gender: string; }’ is not assignable to type ‘Person’.
// Object literal may only specify known properties, and ‘gender’ does not exist in type ‘Person’.

可选属性
可选属性:是指该属性可以不存在。有时不需要完全匹配一个接口时,可以用可选属性,但此时仍然不允许添加未定义的属性

interface Person {
name: string;
age?: number;
}

let p1: Person = { // 编译通过
name: ‘Tom’
};

let p2: Person = { // 编译通过
name: ‘Tom’,
age: 25
};

let p3: Person = { // 报错(同上)
name: ‘Tom’,
age: 25,
gender: ‘male’
};
任意属性
任意属性:允许一个接口有任意的属性

interface Person {
name: string;
age?: number;
[propName: string]: any;
}

let p1: Person = {
name: ‘Tom’,
gender: ‘male’
};
使用 [propName: string]定义了任意属性的属性名取 string 类型的值。属性值为任意值

注意:一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集:

例一:任意属性的类型是 string,但是可选属性 age 的值却是 number,number 不是 string 的子属性,所以会报错。

interface Person {
name: string;
age?: number;
[propName: string]: string;
}

let p1: Person = {
name: ‘Tom’,
age: 25,
gender: ‘male’
};

// Property ‘age’ of type ‘number’ is not assignable to string index type 'string
// Type ‘{ name: string; age: number; gender: string; }’ is not assignable to type ‘Person’.
// Property ‘age’ is incompatible with index signature.
// Type ‘number’ is not assignable to type ‘string’
例二:一个接口中只能定义一个任意属性。如果接口中有多个类型的属性,可以在任意属性中使用联合类型:

interface Person {
name: string;
age?: number;
[propName: string]: string | number;
}

let p1: Person = { // 编译通过
name: ‘Tom’,
age: 25,
gender: ‘male’,
year:2021
};
只读属性
对象中的一些字段只能在创建时被赋值,可以使用 **readonly **定义只读属性:

例一:使用 readonly 定义的属性 id 初始化后,又被重新赋值,所以会报错。

interface Person {
readonly id: number;
name: string;
age?: number;
[propName: string]: any;
}

let p1: Person = {
id: 89757,
name: ‘Tom’,
gender: ‘male’
};

p1.id = 9527;

// Cannot assign to ‘id’ because it is a read-only property.
例二:只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值时:

interface Person {
readonly id: number;
name: string;
age?: number;
[propName: string]: any;
}

let p2: Person = { // 第一次给对象赋值
name: ‘Tom’,
gender: ‘male’
};

p2.id = 89757;

// Property ‘id’ is missing in type ‘{ name: string; gender: string; }’ but required in type ‘Person’ 对 p2 赋值时,没有给 id 赋值

// Cannot assign to ‘id’ because it is a read-only property. id 是只读属性
数组的类型
在 TS 中,有多种定义数组类型的方式。

类型 + 方括号 表示法
最简单的方法是使用类型 + 方括号来表示数组:

let arr: number[] = [1, 1, 2]; // 数组元素中不允许出现其他的类型

let arr1: number[] = [1, ‘1’, 2]; // 报错:Type ‘string’ is not assignable to type ‘number’.
数组的一些方法的参数也会根据数组在定义时约定的类型进行限制:

let arr2: number[] = [1, 1, 2, 3, 5];

arr2.push(‘8’);

//报错:Argument of type ‘“8”’ is not assignable to parameter of type ‘number’.

数组泛型
使用数组泛型(Array Generic) Array来表示数组:

let arr3: Array = [1, 1, 2, 3, 5];
泛型涉及内容较多,后期有时间会在整理一篇文章,敬请关注!

用接口表示数组
之前介绍了使用接口表示对象的类型,同样接口也可以用来描述数组:

interface NumberArray {
[index: number]: number;
}
let arr: NumberArray = [1, 1, 2, 3, 5];
NumberArray 表示:索引的类型是数字,值的类型也是数字,这样便可以表示一个数字类型的数组,虽然接口也可以描述数组,但是一般不会这么做,因为这种方式较复杂。有一例外,就是常用来表示类数组。

类数组
类数组(Array-like Object)不是数组类型,比如 arguments,实际上是一个类数组,不能用普通数组的方式来描述,而应该用接口:

function sum() {
let args: number[] = arguments;
}

// 报错:Type ‘IArguments’ is missing the following properties from type ‘number[]’: pop, push, concat, join, and 24 more.
接口描述类数组:除了约束索引的类型是数字,值的类型也必须是数字之外,也约束了它还有 length 和 callee 两个属性。

function sum() {
let args: {
[index: number]: number;
length: number;
callee: Function;
} = arguments;
}
而事实上常用的类数组都有自己的接口定义,如 IArguments, NodeList, HTMLCollection 等:

function sum() {
let args: IArguments = arguments;
}

//其中 IArguments 是 TypeScript 中定义好了的类型,它实际上就是:
interface IArguments {
[index: number]: any;
length: number;
callee: Function;
}
any 在数组中的应用
一个比较常见的做法是,用 any 表示数组中允许出现任意类型:

let list: any[] = [‘Echoyya’, 25, { website: ‘https://www.cnblogs.com/echoyya/’ }, false];
深圳网站建设www.sz886.com

你可能感兴趣的:(TypeScript 入门自学笔记(一))