TypeScript 初学者入门学习笔记(一)
来源 | https://www.cnblogs.com/echoyya/p/14542005.html
什么是TypeScript?
是添加了类型系统的 JavaScript,适用于任何规模的项目。 是一门静态类型、弱类型的语言。 完全兼容 JavaScript,且不会修改 JavaScript 运行时的特性。 可以编译为 JavaScript,然后运行在浏览器、Node.js 等任何能运行 JavaScript 的环境中。 拥有很多编译选项,类型检查的严格程度可通过配置文件决定。 可以和 JavaScript 共存,这意味着 JavaScript 项目能够渐进式的迁移到 TypeScript。 增强了编辑器(IDE)的功能,提供了代码补全、接口提示、跳转到定义、代码重构等能力。 拥有活跃的社区,大多数常用的第三方库都提供了类型声明,并且开源免费
JavaScript 的缺点
它没有类型约束,一个变量可能初始化时是字符串,又被赋值为数字。 由于隐式类型转换的存在,有些变量的类型很难在运行前就确定。 基于原型的面向对象编程,使得原型上的属性或方法可以在运行时被修改。
为什么使用 TypeScript?
安装
安装:npm install -g typescript,全局安装,可以在任意位置执行tsc
TypeScript 的特性
1、类型系统
TypeScript 是静态类型
// 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。
2、原始数据类型基本使用
布尔值、数值、字符串、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; // 编译通过
3、任意值
1)、任意值(Any)用来表示允许赋值为任意类型。一个普通类型,在赋值过程中是不被允许改变类型的,any 类型,允许被赋值为任意类型。
let str: string = 'abc';
str = 123;
// Type 'number' is not assignable to type 'string'.
// -----------------------------------------------------------------
let str: any = 'abc';
str = 123;
// 编译通过
2)、任意值可以访问任意属性和方法:
let anyThing: any = 'hello';
anyThing.setName('Jerry');
anyThing.setName('Jerry').sayHello();
anyThing.myName.setFirstName('Cat');
console.log(anyThing.myName);
console.log(anyThing.myName.firstName);
// 编译通过
3)、未声明类型的变量:如果变量在声明时,未指定类型,那么会被识别为任意值类型:
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: st
ring | 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 是只读属性
数组的类型
类型 + 方括号 表示法
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<elemType> 来表示数组:
let arr3: Array<number> = [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.
function sum() {
let args: {
[index: number]: number;
length: number;
callee: Function;
} = arguments;
}
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];
学习更多技能
请点击下方公众号