什么是 TypeScript
TypeScript既是一种语言又是一组工具。TypeScript是JavaScript的一个超集。换句话说,TypeScript是JavaScript加上一些额外的功能。
TypeScript 扩展了 JavaScript 的语法,所以任何现有的 JavaScript 程序可以不加改变的在 TypeScript 下工作。TypeScript 是为大型应用之开发而设计,而编译时它产生 JavaScript 以确保兼容性。
TypeScript 可以编译出纯净、 简洁的 JavaScript 代码,并且可以运行在任何浏览器上、Node.js 环境中和任何支持 ECMAScript 3(或更高版本)的 JavaScript 引擎中。
TypeScript的组成部分
语言 - 它包括语法,关键字和类型注释。
编译器 - TypeScript编译器(TSC)将使用TypeScript编写的指令转换为其等效的JavaScript。
语言服务 - “TypeScript语言服务”在核心编译管道周围公开了一个额外的层,它是类似编辑器的应用程序。语言服务支持常见的编辑器操作集,如语句完成,签名帮助,代码格式化和大纲,着色等。
Typescript 官网地址: https://www.typescriptlang.org/zh/
使用 nvm 来管理 node 版本: https://github.com/nvm-sh/nvm
安装 Typescript:
npm install -g typescript
使用 tsc 全局命令:
// 查看 tsc 版本
tsc -v
// 编译 ts 文件
tsc fileName.ts
// 使用 tsc -v 查看是否安装成功, 若成功返回 typescript 版本号
// 链接失败使用淘宝镜像
npm config set registry https://registry.npm.taobao.org
TypeScript 转 JavaScript
- cmd 到文件目录
tsc 文件名.ts
- WebStorm 中自动转换, 勾选 Recompile on change, Webstorm 中勾选如下
类型声明
Boolean、Number
let val2:boolean;
val2 = true;
// val2 = 1; // 会报错
console.log(val2);
let val1:number; // 定义了一个名称叫做val1的变量, 这个变量中将来只能存储数值类型的数据
val1 = 123;
// val1 = "123"; // 会报错
console.log(val1);
String
多行字符串
let hello: string = `Welcome to W3cschool`;
// 类似于 "Welcome to \nW3cschool";
内嵌表达式
let name: string = `Loen`;
let age: number = 37;
let sentence: string = `Hello, my name is ${ name }.
I'll be ${ age + 1 } years old next month.`;
// 类似于 "Hello, my name is " + name + ".\nI'll be " + (age + 1) + " years old next month.";
// 和 format 性质差不多
自动拆分字符串
function userinfo(params,name,age){
console.log(params);
console.log(name);
console.log(age);
}
let myname = "Loen Wang";
let getAge = function(){
return 18;
}
// 调用
userinfo`hello my name is ${myname}, i'm ${getAge()}`
数组和元祖
数组数据类型一致
- Array < number >
// 需求: 要求定义一个数组, 这个数组中将来只能存储数值类型的数据
let arr1: Array<number>; // 表示定义了一个名称叫做arr1的数组, 这个数组中将来只能够存储数值类型的数据
arr1 = [1, 3, 5];
// arr1 = ['a', 3, 5]; // 报错
console.log(arr1);
- string[ ]
// 需求: 要求定义一个数组, 这个数组中将来只能存储字符串类型的数据
let arr2:string[]; // 表示定义了一个名称叫做arr2的数组, 这个数组中将来只能够存储字符串类型的数据
arr2 = ['a', 'b', 'c'];
// arr2 = [1, 'b', 'c']; // 报错
console.log(arr2);
数组数据类型不一致
联合类型声明数组 (number | string)[ ]
let arr3:(number | string)[];
// 表示定义了一个名称叫做arr3的数组, 这个数组中将来既可以存储数值类型的数据, 也可以存储字符串类型的数据
arr3 = [1, 'b', 2, 'c'];
// arr3 = [1, 'b', 2, 'c', false]; // 报错
console.log(arr3);
自由任意类型元素的数组
- 如果不希望类型检查器对值进行检查,直接通过编译阶段的检查。 那么我们可以使用 any 类型来标记这些变量
let notSure: any = 4;
notSure = "这是一个字符串";
notSure = false; // 现在我们又可以将其改成布尔类型
- 当你只知道一部分数据的类型时,any类型也是有用的。 比如,你有一个数组,它包含了不同的类型的数据:
let arr4:any[]; // 表示定义了一个名称叫做arr4的数组, 这个数组中将来可以存储任意类型的数据
arr4 = [1, 'b', false];
console.log(arr4);
严格限制类型和长度的元祖数组
{% note blue ’’ flat %}
TS中的元祖类型其实就是数组类型的扩展,元祖用于保存定长定数据类型的数据
{% endnote %}
let arr5:[string, number, boolean];
// 表示定义了一个名称叫做arr5的元祖, 这个元祖中将来可以存储3个元素, 第一个元素必须是字符串类型, 第二个元素必须是数字类型, 第三个元素必须是布尔类型
arr5 = ['a', 1, true];
// arr5 = ['a', 1, true, false]; // 超过指定的长度会报错
arr5 = ['a', 1, true];
console.log(arr5);
enum枚举
枚举用于表示固定的几个取值,例如: 一年只有四季、人的性别只能是男或者女。 枚举类型是TS为JS扩展的一种类型, 在原生的JS中是没有枚举类型的。
enum Color {Red, Green, Blue}
let c: Color = Color.Green;
// 默认情况下,从0开始为元素编号。 你也可以手动的指定成员的数值
// tips:
enum Color {Red = 1, Green, Blue}
let c: Color = Color.Green;
// 或者
enum Color {Red = 1, Green = 2, Blue = 4}
let c: Color = Color.Green;
/*
枚举类型提供的一个便利是你可以由枚举的值得到它的名字。
例如,我们知道数值为2,但是不确定它映射到Color里的哪个名字,
我们可以查找相应的名字:
*/
enum Color {Red = 1, Green, Blue}
let colorName: string = Color[2];
alert(colorName); // 显示'Green'因为上面代码里它的值是2
Any、Void
any表示任意类型, 当我们不清楚某个值的具体类型的时候我们就可以使用any,任何数据类型的值都可以赋值给any类型, 一般用于定义一些通用性比较强的变量, 或者用于保存从其它框架中获取的不确定类型的值
let value:any; // 定义了一个可以保存任意类型数据的变量
value = 123;
value = "abc";
value = true;
value = [1, 3, 5];
void与any正好相反, 表示没有任何类型, 一般用于函数返回值。在TS中只有null和undefined可以赋值给void类型
function test():void {
console.log("hello world");
}
test();
let value:void; // 定义了一个不可以保存任意类型数据的变量, 只能保存null和undefined
// value = 123; // 报错
// value = "abc";// 报错
// value = true;// 报错
// 注意点: null和undefined是所有类型的子类型, 所以我们可以将null和undefined赋值给任意类型
// value = null; // 不会报错
value = undefined;// 不会报错
Never
表示的是那些永不存在的值的类型,一般用于抛出异常或根本不可能有返回值的函数。
Never 可以赋值给任意类型, 但其他类型不可以赋值给 Never
// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
throw new Error(message);
}
// 推断的返回值类型为never
function fail() {
return error("Something failed");
}
// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
while (true) {
}
}
Object 对象
let obj:object; // 定义了一个只能保存对象的变量
// obj = 1;
// obj = "123";
// obj = true;
obj = {name:'lnj', age:33};
console.log(obj);
interface 接口
基本用法
interface Person {
firstName: string;
lastName: string;
}
function hello(person: Person) {
return "Hello, " + person.firstName + " " + person.lastName;
}
let user = {
firstName: "Wang",
lastName: "Loen"
// else: "error" 会报错
};
document.body.innerHTML = hello(user);
属性数量不确定时的定义方法
少用可选属性
属性名字后面加一个 ?表示可选属性
interface FullName{
firstName:string
lastName:string
middleName?:string
}
function say({firstName, lastName, middleName}:FullName):void {
// console.log(`我的姓名是:${firstName}_${lastName}`);
if(middleName){
console.log(`我的姓名是:${firstName}_${middleName}_${lastName}`);
}else{
console.log(`我的姓名是:${firstName}_${lastName}`);
}
}
say({firstName:'Jonathan', lastName:'Lee', middleName:"666"});
say({firstName:'Jonathan', lastName:'Lee'});
多用索引签名
在定义对象中key(propName)和value的数据结构,后续对象中的属性,只要key和value满足索引签名的限定即可, 无论有多少个都无所谓。
interface FullName {
[propName:string]:string
}
let obj:FullName = {
// 注意点: 只要key和value满足索引签名的限定即可, 无论有多少个都无所谓
firstName:'Jonathan',
lastName:'Lee',
// middleName:false // 报错
// 无论key是什么类型最终都会自动转换成字符串类型, 所以没有报错
// false: '666'
}
interface stringArray {
[propName:number]:string
}
let arr:stringArray = {
0:'a',
1:'b',
2:'c'
};
// let arr:stringArray = ['a', 'b', 'c'];
console.log(arr[0]);
console.log(arr[1]);
console.log(arr[2]);
接口的继承
interface LengthInterface {
length:number
}
interface WidthInterface {
width:number
}
interface HeightInterface {
height:number
}
interface RectInterface extends LengthInterface,WidthInterface,HeightInterface {
// length:number
// width:number
// height:number
color:string
}
let rect:RectInterface = {
length:10,
width:20,
height:30,
color:'red'
}
函数接口
interface SumInterface {
(a:number, b:number):number
}
// 建议使用这种写法
let sum:SumInterface= function(x,y) {
return x + y;
}
let res = sum(10, 20);
console.log(res);
只读属性
可以在属性名前用 readonly来指定只读属性:
interface Point {
readonly x: number;
readonly y: number;
}
可以通过赋值一个对象字面量来构造一个Point。 赋值后, x 和 y 再也不能被改变了。
let p1: Point = { x: 10, y: 20 };
p1.x = 5; // error!
TypeScript 具有 ReadonlyArray 类型,它与 Array 相似,只是把所有可变方法去掉了,因此可以确保数组创建后再也不能被修改:
let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
a = ro; // error!
函数声明
定义函数
// typescript定义函数的方法
// 命名函数
function say1(name:string):void {
console.log(name);
}
// 匿名函数
let say2 = function (name:string):void {
console.log(name);
}
// 箭头函数
let say3 = (name:string):void =>{
console.log(name);
}
函数声明和分离实现
利用 type 声明函数
// 先利用type声明一个函数
type AddFun = (a:number, b:number)=>number;
// 再根据声明去实现这个函数
// 此时函数的参数和返回值可以不需要写类型声明了,因为ts可以通过这个函数声明推断出来类型了
let add:AddFun = function (x, y) {
return x + y;
};
let res = add(30, 20);
console.log(res);
利用 interface 声明函数
// 先利用interface声明一个函数
interface AddFun {
(a:number, b:number):number
}
let add:AddFun = function (x, y) {
return x + y;
};
let res = add(30, 20);
console.log(res);
参数
可选参数
// 需求: 要求定义一个函数可以实现2个数或者3个数的加法
function add(x:number, y:number, z?:number):number {
return x + y + (z ? z : 0);
}
let res = add(10, 20);
let res = add(10, 20, 30);
// 可选参数后面只能跟可选参数
// 可选参数可以是一个或多个
默认参数
function add(x:number, y:number=10):number {
return x + y;
}
let res = add(10);
let res = add(10, 30);
剩余参数
function add(x:number, ...ags:number[]) {
console.log(x);
console.log(ags);
}
add(10, 20, 30, 40, 50)
类型断言
类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起作用。TypeScript 会假设你,程序员,已经进行了必须的检查。
- 类型断言有两种形式。 其一是尖括号语法:
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
- 另一个为as语法:
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
例如: 我们拿到了一个any类型的变量, 但是我们明确的知道这个变量中保存的是字符串类型,此时我们就可以通过类型断言将any类型转换成string类型, 使用字符串类型中相关的方法了。
let str:any = 'it666';
// 当还是any的时候是没有.length的提示的
let len = (str as string).length;
console.log(len);
泛型
什么是泛型
用来弥补any没有语法提示和报错的缺点。
最开始不指定类型,后面根据我们传入的类型确定类型。
软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。在像C#和Java这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。
使用方法
我们给identity添加了类型变量T。 T帮助我们捕获用户传入的类型(比如:number),之后我们就可以使用这个类型。 之后我们再次使用了 T当做返回值类型。现在我们可以知道参数类型与返回值类型是相同的了。 这允许我们跟踪函数里使用的类型的信息。
我们把这个版本的identity函数叫做泛型,因为它可以适用于多个类型。 不同于使用 any,它不会丢失信息,像第一个例子那像保持准确性,传入数值类型并返回数值类型。
泛型类
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };\
// GenericNumber类的使用是十分直观的,并且你可能已经注意到了,没有什么去限制它只能使用number类型。 也可以使用字符串或其它更复杂的类型。
let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "";
stringNumeric.add = function(x, y) { return x + y; };
console.log(stringNumeric.add(stringNumeric.zeroValue, "test"));
//与接口一样,直接把泛型类型放在类后面,可以帮助我们确认类的所有属性都在使用相同的类型。
泛型约束
默认情况下我们可以指定泛型为任意类型,但是有些情况下我们需要指定的类型满足某些条件后才能指定
那么这个时候我们就可以使用泛型约束。
interface IWithLength {
length: number
}
function echoWithLength<T extends IWithLength>(arg: T): T {
console.log(arg.length)
return arg
}
const len01 = echoWithLength('abc') // 3
const len02 = echoWithLength({ length: 12 }) // 12
const len03 = echoWithLength([1, 2, 3]) // 3