TypeScript初识
# 什么是typeScript
- typeScript是javaScript的超集,主要提供了类型系统和对ES6的支持。
- typeScript的代码通过 ts编译工具 可以编译成javaScript代码,从而能在浏览器上运行。类似于java代码需要变成class文件。
- ts代码 => js代码 需要编译, js代码 => ts代码 直接改后缀名为.ts即可,这也是因为ts是js的超集,它包含js但不限于js。
# ts的编译工具
- 上面有提到ts要在浏览器上运行必须编译成js,这里就需要使用ts的编译工具,这个工具其实就是在全局安装tpeyScript。命令如下
-- npm install -g typescript
-- 安装之后我们可以在命令行使用 tsc 命令(类似与javac) 对ts文件进行编译。
-- 例如编译 hello.ts 文件,在命令行执行: tsc hello.ts 命令。这时你的目录里面就会生产一个hello.ts编译之 后的js文件 hello.js
# ts 初体验
- 我们先编写一个hello.ts文件,代码如下
```javaScript (ts)
function sayHello(person: string) {
return 'Hello, ' + person;
}
let userName = 'world';
console.log(sayHello(userName));
```
- 再进行编译 得到hello.js文件,代码如下
```javaScript
function sayHello(person) {
return 'Hello, ' + person;
}
var userName = 'world';
console.log(sayHello(userName));
```
1. 我们可以看到ts代码中sayHello函数的参数为person: string,规定了只能是字符串类型。如果你传入的是其他类型,编译的时候会编译,但是会报错。
# 基本数据类型
- 和js中一样ts也包含布尔值,数值,字符串,null,undefined这集中基本数据类型。当然还有es6中提供的Symbol类型。
## 布尔值类型
- 布尔值作为基本类型中的一种,在ts中使用boolean定义布尔值,注意,不是Boolean,在上面ts初体验中我们定义参数使用的也是string,而不是String
```javaScript (ts)
let isShow : boolean = false; //这里是可以编译通过的
```
- 如果我们ts中像js一样使用构造函数的方法定义一个布尔值是会报错的
```javaScript
let createdByNewBoolean: boolean = new Boolean(1);
// index.ts(1,5): error TS2322: Type 'Boolean' is not assignable to type 'boolean'.
/**
* 事实上在ts中boolean表示的是基本数据类型呢,let createdByNewBoolean: boolean 实际上就是规定了createdByNewBoolean只能是boolean类型。
* 而Boolean无论是在js中还是ts中他都是一个构造函数,通过new构造函数的方式得到值就是一个对象,而对象是属于引用数据类型。
* 其实在js中也是一样,只是js没有如此严格类型系统。
* 这一点ts的基本类型中其他基本类型都是一样,后面就不再赘述(除了null和undefined)
*/
```
## 数值类型
- ts中使用number定义数值类型,直接上代码吧
```javaScript (ts)
let decLiteral: number = 6;
// 十六进制
let hexLiteral: number = 0xf00d;
// ES6 中的二进制表示法
let binaryLiteral: number = 0b1010;
// ES6 中的八进制表示法
let octalLiteral: number = 0o744;
let notANumber: number = NaN;
let infinityNumber: number = Infinity;
```
-以上ts代码经过编译之后
```javaScript
var decLiteral = 6;
// 十六进制数
var hexLiteral = 0xf00d;
// ES6 中的二进制表示法
var binaryLiteral = 10;
// ES6 中的八进制表示法
var octalLiteral = 484;
var notANumber = NaN;
var infinityNumber = Infinity;
/**
* 我们可以看到ts经过编译以后,2进制,8进制都被转化成了10进制数。16进制没有。
* 为啥16进制不转呢?
* Infinity是啥?简单的说(复杂的我也不会)他是表示所有超出javaScript处理范围的数值。他可以进行运算,但是结果不是Infinity(加)就是NaN(乘除减)。
* 我们知道数学中0不能作为除数,任何数除以0都是没有意义的。在js中也是一样,任何数除以0得到的值都是Infinity包括Infinity本身(0除外,0/0=NaN)。
*/
```
## 字符串类型
- 前面有提到ts中使用string来定义字符串
```javaScript (ts)
let myName: string = 'Tom';
let myAge: number = 25;
// 模板字符串
let sentence: string = `Hello, my name is ${myName}.I'll be ${myAge + 1} years old next month.`;
```
- 以上ts代码编译之后得到以下js代码
```javaScript
var myName = 'Tom';
var myAge = 25;
// 模板字符串
var sentence = "Hello, my name is " + myName + ".\nI'll be " + (myAge + 1) + " years old next month.";
/**
* 我们可以到定义的方式语法和数值布尔值类型是一样的,平时我们工作也会使用到es6中模版字符串,使用``包裹模版,${}包裹变量
* 编译之后就回到了我们js中相对low逼的字符串拼接模式。
*/
```
## 空值(ts新增基本类型)
- ts中使用viod来定义空值,但是我们一般不会去定义一个空值,因为你只能给他赋值null或者undefined。
- 我们可以用viod来表示一个函数没有没有任何返回值
```javaScript (ts)
let unusable: void = undefined; // 你只能给他赋值unll和undefined
function alertName(): void { //这表示alertName函数没有任何返回值
alert('My name is Tom');
}
```
## null和undefined
- 这是两种基本数据类型,由于特点相似所以放在一起讲
- null和undefined 是所有类型的子类型,也就是说,不论你定义的任何数据类型的变量都可以赋值null和undefined。
- 这也是null/undefined 和 空值viod的不同之处,被定义为viod类型的变量是不能赋值到其他类型的数据的。
```javaScript (ts)
/**
* 我们前面提到定义void的变量可以给他赋值null和undefined的,
* unll和undefined可以赋值给任何类型的变量,那么我先给void变量u赋值unll, 再将变量u赋值给数值类型n会不会报错呢?
* 如下列代码,是会报错的。
*/
let u:void = null
let n:number = u
let a:number = null
```
## 任意值
- 任意值在ts中使用any定义。
- 个人理解是和js中定义变量一样,当你规定这个变量是一个any类型的时候,你可以给他赋值任何类型的数据。
- 对任意值类型进行任何操作得到的数据都是任意值类型。
```javascript (ts)
let myFavoriteNumber: any = 'seven';
myFavoriteNumber = 7;
console.log(myFavoriteNumber);
myFavoriteNumber = [];
console.log(myFavoriteNumber);
/**
* 当定义后没有马上赋值,并且没有定义类型。默认为任意类型
*/
let arry;
arry = 0;
console.log(arry);
arry = [1,2,3]
console.log(arry);
```
```javascript
var myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
console.log(myFavoriteNumber);
myFavoriteNumber = [];
console.log(myFavoriteNumber);
/**
* 当定义后没有马上赋值,并且没有定义类型。默认为任意类型
*/
var arry;
arry = 0;
console.log(arry);
arry = [1, 2, 3];
console.log(arry);
```
### 类型推论,恰好讲到任意类型先定义后赋值,该定义数据默认为any类型,Ts中还有类型推论规则
- 当你定义数据没有定义类型的时候,并且在定义的同时给他赋值了,那么ts就会推论,你这个数据为你所赋值的类型。
- 这里需要和上面先定义后赋值的情况区分。
- let arry; ==> arry 为任意值类型, let arry = '123' ==> arry 为字符串类型,后续也只能使用字符串为他赋值
## 联合类型
- 顾名思义,表示取值可以为多种类型之一
- 定义类型时使用 | 将类型隔开
```javascript (ts)
let lianhe: string | number = 3;
lianhe = 'seven';
lianhe = 7;
```
```javascript
var lianhe = 3;
lianhe = 'seven';
lianhe = 7;
```
### 访问联合属性的类型和方法
```javascript (ts)
/**
* something是一个联合类型,他可以是string,也可以是number
* 在函数getLength中我们我们尚不清楚他传入的是什么类型,所以只能调取string和number的公共属性或者方法。
*
*/
function getLength(something: string | number){
// return something.length; // 报错
return something.toString(); // 不报错
}
/**
* 联合类型也适用类型推论
*/
let abc : string | number;
abc = 100 ; // 此时犹豫类型推论原则,ts认为abc是一个数字
// console.log(abc.length) // 报错
abc = 'hello' ;
console.log(abc.length) // 5
```
# 引用类型
## 对象的类型-接口
- 在ts中我们使用interfaces来定义对象的类型
- 什么是对象的类型,我是这么理解,他是一个规则,假设你通过某个接口去定义了一个对象,那么这个对象必须按照这个接口规则去写。
- 教程中原文是这么说:TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。
- 一个简单的例子
```javascript
// 定义接口
interface Person { // 接口一般首字母大写。有的编程语言中会建议接口的名称加上 I 前缀。
name: string;
age: number;
}
// 通过定义的接口,来约束对象(定义对象)
let tom: Person = {
name: 'Tom',
age: 25
};
```
### 接口特点
1. 固定属性-当你使用接口去约束你定义的对象后,你为你对象定义的属性/方法,都应该和接口一致,包括数量和命名
2. 可选属性-约束得太死难免会有不愉快的事情发生,所以接口允许设置可选属性, 属性名后面加一个问号即可。
- 可选属性可以不存在。
3. 任意属性
- 使用 [propName: string] 定义了任意属性取 string 类型的值。
- 需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集
- 目前我认为这个属性有点鸡肋,放开一个属性,限制了可选属性和确定属性。
4. 只读属性
- 通过readonly定义只读属性
- 只读属性必须在定义类的时候定义并赋值,后期不可修改。
## 数组类型
1. 在ts中数组类型的定义有很多种
- 「类型 + 方括号」表示法
- 规定了数组成员的类型,不能在该数组中添加其他类型属性(null和undefined除外,上面有讲,他是其他类型子集)
```javascript (ts)
let arryList:number[] = [1,2,3,4,5] // 定义arrayList为一个数值数组
console.log(arryList);
arryList.push(10);
arryList.push(null);
arryList.push(undefined);
// arryList.push('1'); // 往里面添加字符串是会报错的。
console.log(arryList);
// 当然如果你定义的类型为any,那你就可以随意push进任意数据类型了
```
2. 用接口定义数组 (接口是用来定义对象的类型和形状的。万物皆对象,数组也是对象咯)
```javascript (ts)
interface NewArry {
[a : number] : number; //定义这个数组对象的每一项只能是数值
}
let arr1:NewArry = [1,2,3,4,"1"];
```
3. 还有一种方式叫做数组泛型,在此不做分享,后面进阶内容有详细解说。
## 类数组
- 类数组不属于数组类型,比如arguments:
- 事实上常见的类数组都有自己的接口定义,如 IArguments, NodeList, HTMLCollection 等:
```javascript (ts)
function sum() {
let args: number[] = arguments; // 会报错
}
function sum2() {
let args: IArguments = arguments; // 使用他们自己的接口定义就不会报错
}
```
## 函数
- 在ts中函数也和js中一样,使用函数声明和函数表达式定义函数
- 不同的是ts对函数输入和输出进行了更多的约束
1. 定义规则
- 输入多余的(或者少于要求的)参数,是不被允许的。
2. 函数声明
```javascript (ts)
function fn1( x : number,y : number ) : number {
return x+y
}
```
3. 函数表达式
```javascript (ts)
/**
* 这里的 => 要和es6中的箭头函数区分,=> 在这里 左边表示函数的参数定义,右边表示函数的返回值定义。
*/
let fn2 : ( x: number, y : number ) => number = function( x: number, y: number) : number {
return x+y
}
```
4. 接口定义函数
```javascript (Ts)
// 定义接口
interface SearchFunc {
(source: string, subString: string): boolean;
}
// 接口约束函数
let fn3: SearchFunc;
fn3 = function(source: string, subString: string) {
return source.search(subString) !== -1;
}
```
5. 可选参数
- 可选参数使用 ?标识
- 需要注意的是,可选参数必须接在必需参数后面。换句话说,可选参数后面不允许再出现必须参数了
```javascript (ts)
function fn4(firstName: string, lastName?: string) {
if (lastName) {
return firstName + ' ' + lastName;
} else {
return firstName;
}
}
let tomcat = fn4('Tom', 'Cat');
let tom = fn4('Tom');
```
6. 参数默认值
- 在 ES6 中,我们允许给函数的参数添加默认值,TypeScript 会将添加了默认值的参数识别为可选参数
- 此时就不受「可选参数必须接在必需参数后面」的限制了
```javascript (Ts)
function fn5(firstName: string = 'Tom', lastName: string) {
return firstName + ' ' + lastName;
}
let tomcat = fn5('Tom', 'Cat');
let cat = fn5(undefined, 'Cat');
```
7. 剩余参数
- ES6 中,可以使用 ...rest 的方式获取函数中的剩余参数(rest 参数)
- 剩余参数只能是最后一个参数
```javascript (Ts)
function push1(array, ...items) {
items.forEach(function(item) {
array.push(item);
});
}
let a = [];
push(a, 1, 2, 3);
// 事实上,剩余参数 items 是一个数组。所以我们可以用数组的类型来定义它:
function push2(array: any[], ...items: any[]) {
items.forEach(function(item) {
array.push(item);
});
}
let a = [];
push2(a, 1, 2, 3);
```
8. 重载
- 什么是重载,就是函数的方法名相同,但是参数不同,调用的时候根据参数的不同去执行不同的执行体。
- js中不存在重载,如果方法名相同,他永远执行的是写在最后的一个方法。
- 重载允许一个函数接受不同数量或类型的参数时,作出不同的处理。
- 比如,我们需要实现一个函数 reverse,输入数字 123 的时候,输出反转的数字 321,输入字符串 'hello' 的时候,输出反转的字符串 'olleh'。
- 利用联合类型,我们可以这么实现
```javascript (Ts)
function reverse1( x: string | number) : number|string{
if( typeof x === 'number') {
return Number(x.toString().slipt('').reverse().join(''))
}else if(typeof x === 'string'){
return x.slipt('').reverse().join('')
}
}
/**
* 然而这样有一个缺点,就是不能够精确的表达,输入为数字的时候,输出也应该为数字,输入为字符串的时候,输出也应该为字符串。这时,我们可以使用重载定义多个 reverse 的函数类型
*/
function reverse2(x: number): number;
function reverse2(x: string): string;
function reverse2(x: number | string): number | string {
if (typeof x === 'number') {
return Number(x.toString().split('').reverse().join(''));
} else if (typeof x === 'string') {
return x.split('').reverse().join('');
}
}
/**
* 上例中,我们重复定义了多次函数 reverse,前几次都是函数定义,最后一次是函数实现。
* 注意,TypeScript 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面
*/
```
## 类型断言
1. 语法
- <类型>值
- 值 as 类型 ==> 在 tsx 语法(React 的 jsx 语法的 ts 版)中必须用后一种。
2. 意义
- 之前提到过,当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法
- 这时我们就可以使用类型断言,将一个联合类型的变量指定为一个更加具体的类型,从而使用这个类型的属性
3. 类型断言,不是类型转换,断言一个联合类型中不存在的类型是会报错的。
```javascript (ts)
function getLength(something: string | number): number {
if (something.length) {
return something.length;
} else {
return something.toString().length;
}
}
// 以上代码是会报错的。在没有断言的情况下,只能直接访问联合类型的公共属性
function getLength(something: string | number): number {
if ((<string>something).length) {
return (<string>something).length;
} else {
return something.toString().length;
}
}
```
## 内置对象
1. JavaScript 中有很多内置对象,它们可以直接在 TypeScript 中当做定义好了的类型。
- 内置对象是指根据标准在全局作用域(Global)上存在的对象。这里的标准是指 ECMAScript 和其他环境(比如 DOM)的标准。
2. ECMAScript
- 标准提供的内置对象有 : Boolean、Error、Date、RegExp 等
- 我们可以在 TypeScript 中将变量定义为这些类型:
```javascript (ts)
let b: Boolean = new Boolean(1);
let e: Error = new Error('Error occurred');
let d: Date = new Date();
let r: RegExp = /[a-z]/;
```
3. DOM 和 BOM 提供的内置对象有:Document、HTMLElement、Event、NodeList 等。
```javascript (ts)
let body: HTMLElement = document.body;
let allDiv: NodeList = document.querySelectorAll('div');
document.addEventListener('click', function(e: MouseEvent) {
// Do something
});
```
4. 使用ts写node.js
- Node.js 不是内置对象的一部分,如果想用 TypeScript 写 Node.js,则需要引入第三方声明文件:
npm install @types/node --save-dev