TypeScript 入门自学笔记(一)


码文不易,转载请带上本文链接,感谢~

目录
  • TypeScript 介绍
    • 什么是TypeScript?
    • JavaScript 的缺点
    • 为什么使用 TypeScript?
    • 安装
  • TypeScript 的特性
    • 类型系统
      • TypeScript 是静态类型
      • TypeScript 是弱类型
    • 原始数据类型基本使用
      • 数值
      • 字符串
      • 空值 及(与Null 和 Undefined的区别)
    • 任意值
    • 类型推论
    • 联合类型
    • 对象的类型——接口
      • 确定属性
      • 可选属性
      • 任意属性
      • 只读属性
    • 数组的类型
      • 类型 + 方括号 表示法
      • 数组泛型
      • 用接口表示数组
      • 类数组
      • any 在数组中的应用

TypeScript 介绍

首先介绍一下什么是TypeScript ,与JavaScript的区别,及优缺点

什么是TypeScript?

  1. 是添加了类型系统的 JavaScript,适用于任何规模的项目。
  2. 是一门静态类型、弱类型的语言。
  3. 完全兼容 JavaScript,且不会修改 JavaScript 运行时的特性。
  4. 可以编译为 JavaScript,然后运行在浏览器、Node.js 等任何能运行 JavaScript 的环境中。
  5. 拥有很多编译选项,类型检查的严格程度可通过配置文件决定。
  6. 可以和 JavaScript 共存,这意味着 JavaScript 项目能够渐进式的迁移到 TypeScript。
  7. 增强了编辑器(IDE)的功能,提供了代码补全、接口提示、跳转到定义、代码重构等能力。
  8. 拥有活跃的社区,大多数常用的第三方库都提供了类型声明,并且开源免费

JavaScript 的缺点

首先JavaScript 是一门非常灵活的编程语言:

  • 它没有类型约束,一个变量可能初始化时是字符串,又被赋值为数字。
  • 由于隐式类型转换的存在,有些变量的类型很难在运行前就确定。
  • 基于原型的面向对象编程,使得原型上的属性或方法可以在运行时被修改。

TypeScript 的类型系统,在很大程度上弥补了 JavaScript 的缺点。

为什么使用 TypeScript?

一般来说,在大型项目中,后期维护成本比前期开发成本要多得多,所以团队规范化尤为重要,包括编码规范,方法调用规范等,而TS可以通过代码的方式,约束团队开发,这样才有利于后期维护及扩展,从而达到高效的开发

两个最重要的特性——类型系统适用于任何规模

优势:强大的IDE支持,支持类型检测,允许为变量指定类型,语法检测,语法提示

缺点:有一定的学习成本,需要理解 接口,泛型,类,枚举类型等前端可能不是很熟悉的知识点。

接口(Interfaces):可以用于对``对象的形状Shape`进行描述

泛型(Generics):在定义函数,接口或类时,不预先指定具体的类型,而是在使用时在指定类型的一种特性

类(Classes):定义了一件事物的抽象特点,包括属性和方法

安装

若想使用TS进行开发,首先必须要搭建搭建TypeScript开发环境
安装: npm install -g typescript,全局安装,可以在任意位置执行tsc

版本:tsc -v

编译:tsc 文件名.ts

TS 中,使用:为变量指定类型,: 前后的空格可有可无。TS 只会在编译时对类型进行静态检查,如发现有错误,编译时就会报错。而在运行时,与普通的 JavaScript 文件一样,不会对类型进行检查。

编译时即使报错,还是会生成编译结果,仍然可以使用编译之后的文件,若想在报错时终止 js文件的生成,可以在 tsconfig.json 中配置 noEmitOnError 即可。

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|0B0o|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;     // 编译通过

任意值

  1. 任意值(Any)用来表示允许赋值为任意类型。一个普通类型,在赋值过程中是不被允许改变类型的,any 类型,允许被赋值为任意类型。
let str: string = 'abc';
str = 123;
 
// Type 'number' is not assignable to type 'string'.
  
// -----------------------------------------------------------------
  
let str: any = 'abc';
str = 123;
  
// 编译通过
  1. 任意值可以访问任意属性和方法:
let anyThing: any = 'hello';
   
anyThing.setName('Jerry');
anyThing.setName('Jerry').sayHello();
anyThing.myName.setFirstName('Cat');
   
console.log(anyThing.myName);
console.log(anyThing.myName.firstName);
   
// 编译通过
  1. 未声明类型的变量:如果变量在声明时,未指定类型,那么会被识别为任意值类型:
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];

后续笔记敬请期待~~~

相关