开篇
- 什么是
TypeScript(TS)?
TypeScript简称TS
TS和JS之间的关系其实就是Less/Sass和CSS之间的关系
就像Less/Sass是对CSS进行扩展一样,TS也是对JS进行扩展
就像Less/Sass最终会转换成CSS一样,我们编写好的TS代码最终也会转换成JS
- 为什么需要
TypeScript?
因为JavaScript是弱类型,很多错误只有在运行时才会被发现
而TypeScript是强类型,它提供了一套静态检测机制,可以帮助我们在编译时就发现错误
-
TypeScript特点支持最新的
JavaScript新特性支持代码静态检查
支持注入
C、C++、Java、Go等后端语言中的特性(枚举、泛型、类型转换、命名空间、声明文件、类、接口等)
数据类型
基础数据类型
TypeScript支持与JavaScript几乎相同的数据类型,此外还提供了实用的枚举类型方便我们使用
// 数值类型 number
// 定义了一个名称叫做value1的变量,这个变量将来只能存储数值类型的数据
let value1:number;
value1 = 123;
// 布尔类型 boolean
// 定义了一个名称叫做value2的变量,这个变量将来只能存储布尔类型的数据
let value2:boolean;
value2 = true;
// 字符串类型 string
// 定义了一个名称叫做value3的变量,这个变量将来只能存储字符串类型的数据
let value3:string;
value3 = 'hello world!';
value3 = `你好世界!`数组和元祖类型
// 数组类型
// 方式一
// 表示定义了一个名叫arr1的数组,这个数组将来只能用来存储数值类型的数据
let arr1:Array<number>;
arr1 = [1, 3, 5];
// 方式二
// 表示定义了一个名叫arr2的数组,这个数组将来只能用来存储字符串类型的数据
let arr2:string[];
arr2 = ['2', '4', '6'];
// 联合类型
// 表示定义了一个名叫arr3的数组,这个数组将来只能用来存储字符串类型的数据和字符串类型的数据
let arr3:(number | string)[];
arr3 = [1, '2', 3];
// 任意类型
// 表示定义了一个名叫arr4的数组,这个数组将来可以存储任意类型的数据
let arr4:any[];
arr4 = [1, '2', true];
// 元祖类型
// TS中的元祖类型其实就是数组类型的扩展
// 元祖用于保存定长定数据类型的数据
// 表示定义了一个名叫arr5的数组,这个数组将来可以存储3个元素,第一个元素必须是字符串类型,第二个元素必须是数字类型,第三个元素必须是布尔类型
let arr5:[string, number, boolean];
arr5 = ['1', 2, false];枚举类型
枚举类型是TS为JS扩展的一种类型,在原生的JS中是没有枚举类型的
枚举用于表示固定的几个取值
例如:一年只有四季、人的性别
注意点:
- TS中的枚举底层实现的本质其实就是数值类型,所以赋值一个数值不会报错
- TS中的枚举类型的取值,默认是从上至下从0开始递增的
- 虽然默认是从0开始递增的,但是我们也可以手动的指定枚举的取值的值
- 如果手动指定了前面枚举的取值,那么后面的枚举值的取值会根据前面的值来递增
- 如果手动指定了后面枚举的取值,那么前面的枚举值的取值不会受到影响
- 我们还可以同时修改多个枚举值的取值,如果同时修改了多个,那么修改的是什么最后就是什么
enum Gender{
Male,
Female,
}
// 定义了一个名称叫做value的变量,这个变量中只能保存Male或者Female
let value:Gender;
value = Gender.Male;
value = Gender.Female;
// 枚举数值的实现原理
let Gender = {};
Gender[Gender["Male"] = 0] = "Male";
console.log(Gender["Male"]); // 0
console.log(Gender[0]); // Maleany类型
any表示任意类型,当我们不清楚某个值的具体类型的时候我们就可以使用any
一般用于定义一些通用型比较强的变量,或者用于保存从其它框架中获取不确定类型的值
在TS中任何类型都可以赋值给any类型
// 定义了一个可以保存任意类型数据的变量
let value:any;
value = 123;
value = 'hello world!';
value = false;
value = [1, 3, 5];void类型
void类型与any正好相反,表示没有任何类型,一般用于函数返回值
在TS中只有null和undefined可以赋值给void类型
注意点:
null和undefined是所有类型的子类型,所以我们可以将null和undefined赋值给任意类型
// 定义了一个没有返回值的函数
function test():void{
console.log('hello world!');
}
test();never类型
表示的是那些永不存在的值的类型
一般用于抛出异常或根本不可能有返回值的函数
function demo():never{
throw new Error('报错了')
}
demo();
function demo2():never{
while(true){}
}
demo2();object类型
表示一个对象
let obj:object;
obj = {name: 'zs', age: 18}类型断言
TS中的类型断言和其它编程语言的类型转换很像,可以将一种类型强制转换成另一种类型。
类型断言就是告诉编辑器,你不要帮我们检查了,相信我,我知道自己在干什么。
注意点:
企业开发中推荐使用as来进行类型转换(类型断言)
因为第一种方式有兼容性问题,在使用到JSX的时候兼容性不是很好
/*
* 例如:
* 我们拿到了一个any类型的变量,但是我们明确的知道这个变量中保存的是字符串类型
* 此时我们就可以通过类型断言告诉编译器,这个变量是一个字符串类型
* 此时我们就可以通过类型断言将any类型转换成string类型,使用字符串类型中相关的方法了
* */
let str:any = 'hello world!';
// 方式一
// let len = (<string>str)
// console.log(len.length);
// 方式二
let len = (str as string);
console.log(len.length);接口类型
接口也是一种类型,也是用来约束使用者的
// 需求:要求定义一个函数输出一个人完整的姓名,这个姓必须是字符串,这个人的命也必须是一个字符串
interface FullName{
firstName: string,
lastName: string,
}
let obj = {
firstName: 'hello',
lastName: 'world'
}
function say({firstName, lastName}:FullName):void{
console.log(`我的姓名是:${firstName}${lastName}`)
}
say(obj);可选属性
让对象属性可选是否传入
// 对象属性可以少一个或者少多个
interface FullName{
firstName: string;
lastName: string;
middleName?: string; // 可选属性
[propName: string]: any; // 索引签名
}
function say({firstName, lastName}:FullName){
console.log(firstName, lastName);
}
say({firstName: 'hello', lastName: 'world'})
// 对象属性可以多一个或者多多个
// 方式一:类型断言
say({firstName: 'hello', lastName: 'world', middleName: '!', abc: 'abc'} as FullName)
// 方式二:使用变量(不推荐)
let obj = {firstName: 'hello', lastName: 'world', middleName: '!', abc: 'abc'};
say(obj);
// 方式三:索引签名
say({firstName: 'hello', lastName: 'world', middleName: '!', abc: 'abc'});索引签名
索引签名用于描述那些“通过索引得到的类型”,比如arr[0]或obj['key']
// 索引签名
interface FullName {
[propName: string]: string;
}
// 只要key和value满足索引签名的限制即可,无论有多少个无所谓
let obj:FullName = {
firstName: 'John',
lastName: 'Doe',
false: 'hello', // 无论key是什么类型最终都会自动转换成字符串类型,所以没有报错
}
interface StringArray {
[propName: number]: string;
}
let arr:StringArray = {
0: 'a',
1: 'b',
2: 'c',
}
let arr2:StringArray = ['a', 'b', 'c'];只读属性
让对象属性只能在对象刚刚创建的时候修改值
// 只读属性
interface FullName {
firstName: string;
readonly lastName: string;
}
let myName:FullName = {
firstName: 'hello',
lastName: 'world',
}
// 报错: Cannot assign to lastName because it is a read-only property
// myName.lastName = '世界';
// TS内部对只读属性进行了扩展,扩展出来了一个只读数组
let arr:ReadonlyArray<string> = ['a', 'b', 'c'];
console.log(arr[0]);
// 报错: Index signature in type readonly string[] only permits reading
// arr[0] = 'abc'函数接口
除了可以通过接口来限定对象以外,我们还可以使用接口来限定函数
// 函数接口
interface SumInterface {
(a: number, b: number): number
}
let sum:SumInterface = function (x: number, y: number):number{
return x + y
}
sum(1, 2);混合类型接口
// 要求定义一个函数实现变量累加
interface CountInterface {
(): void;
count: number;
}
let getCounter = (function():CountInterface{
/*
CountInterface接口要求数据既要是一个没有参数返回值的函数
又要是一个拥有count属性的对象
fn作为函数的时候符合接口中函数接口的限定 ():void
fn作为对象时符合接口中对象接口的限定 {count:number}
* */
let fn = <CountInterface>function(){
fn.count++
console.log(fn.count)
}
fn.count = 0;
return fn
})();
getCounter();
getCounter();
getCounter();接口继承
TS中的接口和JS中的类一样是可以继承的
// 接口的继承
interface LengthInterface {
length: number
}
interface WidthInterface {
width: number
}
interface HeightInterface {
height: number
}
interface RectInterface extends LengthInterface, WidthInterface, HeightInterface {
color: string
}
let rect:RectInterface = {
length: 10,
width: 20,
height: 30,
color: 'red'
}函数
// TS中的函数
// TS中的函数大部分和JS相同
// 命名函数
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 AddFun = (a: number, b: number) => number;
let add:AddFun = function(x, y){
return x + y;
}
let res = add(10, 20);函数重载
函数的重载就是同名的函数可以根据不同的参数来实现不同的功能
// 函数重载
/*
function getArray(x:number):number[]{
let arr = [];
for(let i = 0; i < x; i++){
arr.push(i)
}
return arr
}
function getArray(str:string):string[]{
return str.split('')
}
*/
// 定义函数重载
function getArray(x:number):number[];
function getArray(str:string):string[];
// 实现函数的重载
function getArray(x:any):any{
if(typeof x === 'number'){
let arr = [];
for(let i = 0; i < x; i++){
arr.push(i)
}
return arr
}else{
return x.split('')
}
}
let res = getArray(10);
console.log(res); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
let res2 = getArray('hello world!');
console.log(res2); // ['h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!']可选参数
// 可选参数
// 需求:要求定义一个函数可以实现2个数或者3个数的加法
/*
function add(x: number, y: number, z?: number):number{
return x + y + (z || 0);
}
add(10, 20);
*/
// 可选参数可以配合函数重载一起使用,可以让函数重载变得更加强大
function add(x: number, y: number):number;
function add(x: number, y: number, z: number):number;
function add(x: number, y: number, z?: number):number{
return x + y + (z || 0);
}
add(10, 20, 30);默认参数
// 默认参数
function add(x:number, y:number=20){
return x + y;
}
add(10);
add(10, 30);剩余参数
// 剩余参数
function add(x:number, ...args:number[]):void{
console.log(x);
console.log(args)
}
add(10,20,30,40,50);泛型
在编写代码的时候我们既要考虑代码的健壮性,又要考虑代码的灵活性和可重性
通过TS的静态检测能让我们编写的代码更加健壮,但是在变得健壮的同时却丢失了灵活性和重用性
所以为了解决这个问题TS推出了泛型的概念
通过泛型不仅可以让我们的代码变得更加健壮,还能让我们的代码在变得健壮的同时保持灵活性和可重用性
// 泛型
// 需求:定义一个创建数组的方法,可以创建出指定长度的数组,并且可以用任意指定的内容填充这个数组
let getArr = <T>(value:T, items:number):T[] => {
return new Array(items).fill(value);
}
// 需求:要有代码提示,如果写错了要在编译的时候报错
// let arr = getArr(6, 3);
let arr = getArr('abcd', 3);
console.log(arr);
arr.map(item => item.length)泛型约束
默认情况下我们可以指定泛型为任意类型
但是有些情况下我们需要指定的类型满足某些条件后才能指定
那么这个时候我们就可以使用泛型约束
// 泛型约束
interface LengthInterface {
length:number;
}
let getArr = <T extends LengthInterface>(value:T, items:number = 5):T[] => {
return new Array(items).fill(value);
}
let res = getArr('abcdefg', 3);
res.map(item => item.length)
console.log(res)约束中使用类型参数
一个范型被另一个范型约束,就叫做范型约束中使用类型参数
// 泛型约束使用类型参数
// 需求:定义一个函数用于根据指定的key获取对象的value
let getProps = <T, K extends keyof T>(obj: T, key: K):any => {
return obj[key];
}
let obj = {
name: 'why',
age: 18
}
let res = getProps(obj, 'name');类
TS中的类和ES6中的类几乎是一样的
export class Person {
// 和ES6区别,需要先定义实例属性,才能够使用实例属性
// 实例属性 实例子方法
name:string;
age:number;
constructor(name:string, age:number){
this.name = name;
this.age = age;
}
say():void{
console.log(`我的名字是${this.name},我的年龄是${this.age}`);
}
// 静态属性 静态方法
static food:string;
static eat():void{
console.log(`我正在吃${this.food}`)
}
}
let p = new Person('张三', 18);
p.say();
Person.food = '雪糕';
Person.eat();
// 继承
/*
class Student extends Person{
constructor(name:string, age:number){
super(name, age);
}
}
let stu = new Student('李四', 19);
stu.say();
Student.food = '冰淇淋';
Student.eat();
*/
// 覆写
class Student extends Person{
book:string;
constructor(name:string, age:number, book:string){
super(name, age);
this.book = book;
}
say():void{
console.log(`覆写之后的:我叫${this.name},我今年${this.age}岁,我正在看${this.book}`);
}
static eat():void{
console.log(`覆写之后的:吃${Student.food}`);
}
}
let stu = new Student('王五', 20, '三体');
stu.say();
Student.food = '瘦猪肉';
Student.eat();属性修饰符
public(公开的):
如果使用public来修饰属性,那么表示这个属性是公开的
可以在类的内部使用,也可以在子类中使用,也可以在外部使用
protected(受保护的):
如果使用protected来修饰属性,那么表示这个属性是受保护的 可以在类的内部使用,可以在子类中使用,不可以再外部使用
private(似有的):
如果使用private来修饰属性,那么表示这个属性是私有的
可以在类的内部中使用
readonly(只读的)
export class Person {
public name:string;
// 属性“age”受保护,只能在类“Person”及其子类中访问
protected age:number;
// 属性“gender”为私有属性,只能在类“Person”中访问
private gender:string;
// 无法为“height”赋值,因为它是只读属性
readonly height:number;
constructor(name:string, age:number, gender:string, height:number){
this.name = name;
this.age = age;
this.gender = gender;
this.height = height;
}
say():void{
console.log(`我的名字是${this.name},我的年龄是${this.age},我的性别是${this.gender},我的身高是${this.height}`);
}
}
class Student extends Person{
constructor(name:string, age:number, gender:string, height:number){
super(name, age, gender, height);
}
say():void{
//
console.log(`覆写之后的:我叫${this.name},我今年${this.age}岁,我的身高是${this.height}`);
}
}
let p = new Person('张三', 18, 'male', 180);
p.say();
// p.height = 190; // 报错
let s = new Student('李四', 18, 'female', 176);
s.say();
// console.log(s.name);
// console.log(s.age); // 报错方法修饰符
方法修饰符和属性修饰符一样
// 奇淫技巧
// 需求:有一个基类,所有的子类都需要继承这个基类,但是我们不希望别人通过这个基类来创建对象
class Person{
name:string;
age:number;
protected constructor(name:string, age:number){
this.name = name;
this.age = age;
}
say():void{
console.log(`我的名字是${this.name},我的年龄是${this.age}`);
}
}
class Student extends Person{
constructor(name:string, age:number){
super(name, age);
}
say():void{
console.log(`我的名字是${this.name},我的年龄是${this.age}`);
}
}
// let p = new Person('张三', 18);
let s = new Student('李四', 18);可选属性
和接口中的可选属性一样,可传可不传的属性
// 可选属性
class Person {
name:string;
age?:number;
constructor(name:string, age?:number){
this.name = name;
this.age = age;
}
}
let p = new Person('张三');参数属性
// 参数属性
/*
class Person {
name:string;
age:number;
constructor(name:string, age:number) {
this.name = name;
this.age = age;
}
}
*/
// 简化上面代码
class Person {
constructor(public name:string, public age:number) {
}
}
let p = new Person('hello', 18);
console.log(p);存取器
通过getters/setters来截取对对象成员的访问
// 存取器
class Person{
private _age:number = 0;
set age(val){
console.log('进入了set age方法');
if(val < 0){
throw new Error('输入的数字不能小于0');
}
this._age = val;
}
get age():number{
console.log('进入了get age方法')
return this._age;
}
}
let p = new Person();
// p.age = 10; // p.set(10);
p.age = 34;
console.log(p.age);抽象类
-
什么是抽象类
抽象类是专门用于定义那些不希望被外界直接创建的类的
抽象类一般用于定义基类
抽象类和接口一样用于约束子类
-
抽象类和接口区别
接口中只能定义约束,不能定义具体实现
尔抽象类中既可以定义约束,又可以定义具体实现
export {}
// 抽象类
abstract class Person{
abstract name:string;
abstract say():void;
eat():void {
console.log(`${this.name}正在吃东西`);
}
}
class Student extends Person{
name:string = '张三';
say():void{
console.log(`我的名字是${this.name}`)
}
}
let stu = new Student();
stu.say();
stu.eat();类和接口
// 类实现接口
/*
interface PersonInterface {
name:string;
say():void;
}
// 只要实现的某一个接口,那么就必须实现接口中所有的属性和方法
class Person implements PersonInterface{
name:string = '张三';
say(): void {
console.log(`我的名字是${this.name}`);
}
}
let p = new Person();
p.say();
*/
// 接口继承类
class Person {
protected name:string = '张三';
age:number = 18;
protected say():void{
console.log(`name:${this.name},age:${this.age}`);
}
}
// 只要一个接口继承了某个类,那么就会继承这个类中所有的属性和方法
// 但是只会继承属性和方法的声明,不会继承属性和方法的实现
// 如果接口继承的类中包含了protected的属性和方法,那么就只有这个类的子类才能实现这个接口
interface PersonInterface extends Person{
book:string;
}
class Student extends Person implements PersonInterface{
book:string = '《人间》';
name:string = '李四';
age:number = 20;
say():void{
console.log(`name:${this.name},age:${this.age},book:${this.book}`);
}
}
let stu = new Student();
stu.say();泛型
// 泛型类
class Cache<T>{
arr:T[] = [];
add(value:T):T{
this.arr.push(value);
return value;
}
all():T[]{
return this.arr;
}
}
let cache = new Cache<number>();
cache.add(1);
cache.add(3);
cache.add(5);
console.log(cache.all());接口合并现象
当我们定义了多个同名的接口时,多个接口的内容会自动合并
// 接口合并
interface TestInterface{
name:string;
}
interface TestInterface{
age:number;
}
class Person implements TestInterface{
name:string;
age:number;
}枚举
TS中支持两种枚举,一种是数字枚举,一种是字符串枚举
// 数字枚举
// 默认情况下是数字枚举
/*
enum Gender{
Male,
Female
}
*/
// 数字枚举的取值默认是从0开始递增
// 数字枚举的取值可以是字面量,也可以是常量,也可以是计算的结果
/*
const num = 666;
function getNum(){
return 234;
}
enum Gender{
// Male = 6,
// Male = num,
// Female
Male = getNum(), // 如果使用了计算结果给前面的枚举赋值了,那么后面的枚举值也需要手动的赋值
Female = 235
}
console.log(Gender, 'gender');
*/
// 枚举反向映射
// 可以根据枚举值获取到原始值
// 可以根据原始值获取到枚举值
/*
enum Gender{
Male,
Female
}
console.log(Gender.Male);
console.log(Gender[0]);
*/
// 字符串枚举
// 和数字枚举不一样,字符串枚举不能使用常量或者计算结果给枚举赋值
/*
enum Gender{
Male = '男', // 如果使用了字符串给前面的枚举赋值了,那么后面的枚举值也需要手动的赋值
Female = '女'
}
*/
// 异构枚举
// 枚举中既包含数字又包含字符串,我们称之为异构枚举
// 如果是字符串枚举,那么无法通过原始值获取到枚举值
enum Gender{
Male = '男',
Female = 1
}
console.log(Gender.Male);
console.log(Gender['男']); // undefined
console.log(Gender.Female);
console.log(Gender[1]);成员类型
// 枚举成员类型
enum Gender{
Male,
Female,
}
interface TestInterface {
age: Gender.Male
}
class Person implements TestInterface{
age: Gender.Male;
// age: 0; // 由于数字枚举的本质就是数值,所以这里少写一个数值也不会报错.如果是字符串类型,那么只能是枚举的值,不能是其它的值
}联合类型
把枚举类型当作一个联合类型来使用
// 联合枚举类型
enum Gender {
Male,
Female
}
interface GenderInterface {
age: Gender;
}
class Person implements GenderInterface{
// age: Gender.Male;
age: Gender.Female;
}运行时枚举
枚举在编译之后是一个真实存储的对象,所以可以在运行时使用
而像接口这种只是用来做静态检查的代码,编译之后是不存在的
interface TestInterface{
name:string;
age:number;
}
enum Gender{
Male,
Female
}
/*
编译后代码
var Gender;
(function (Gender) {
Gender[Gender["Male"] = 0] = "Male";
Gender[Gender["Female"] = 1] = "Female";
})(Gender || (Gender = {}));
*/常量枚举
普通枚举和常量枚举的区别
普通枚举会生成真实存在的对象
常量枚举不会成圣真实存在的对象,而是利用枚举成员的值直接替换使用到的地方
enum Gender1{
Male,
Female
}
console.log(Gender1.Male === 0);
const enum Gender2{
Male,
Female
}
console.log(Gender2.Male === 0);
/*
编译后代码
var Gender1;
(function (Gender1) {
Gender1[Gender1["Male"] = 0] = "Male";
Gender1[Gender1["Female"] = 1] = "Female";
})(Gender1 || (Gender1 = {}));
console.log(Gender1.Male === 0);
console.log(0 /* Gender2.Male */ === 0);
*/类型推断
-
什么是自动类型推断?
不用明确告诉编译器具体是什么类型,编译器就知道是什么类型
-
根据初始值自动推断
// 根据初始值自动推断 // let value:number = 123; let value = 123; value = 234; // let arr:(number | string) = [1, 'a']; let arr = [1, 'a']; arr = [1, 5, 6]; -
根据上下文类型自动推断
// 根据上下文自动推断 window.onmousedown = (event) => { console.log(event.target) }
-
兼容性
基本兼容性
interface TestInterface{
name:string
}
let p1 = {name:'张三'};
let p2 = {age:18};
let p3 = {name:'张三', age:18};
let t:TestInterface;
t = p1;
// t = p2; // 报错:类型 "{ age: number; }" 中缺少属性 "name",但类型 "TestInterface" 中需要该属性
t = p3;
interface Test2Interface{
name:string;
children: {
age:number;
}
}
let p4 = {name:'张三', children:{age:18}};
let p5 = {name:'李四', children:{age: 'abc'}};
let t2:Test2Interface;
t2 = p4;
// t2 = p5; // 报从:会递归检查函数兼容性
export {}
// 参数个数
/*
let fn1 = (x:number, y:number) => {};
let fn2 = (x:number) => {};
fn1 = fn2;
// fn2 = fn1; // 不可将参数多的赋给参数少的
*/
// 参数类型
/*
let fn1 = (x:number) => {};
let fn2 = (x:number) => {};
let fn3 = (x:string) => {};
fn1 = fn2;
fn2 = fn1;
// fn1 = fn3; // 必须一模一样
// fn3 = fn1;
*/
// 返回值类型
/*
let fn1 = ():number => 123;
let fn2 = ():number => 234;
let fn3 = ():string => 'abc';
fn1 = fn2;
fn2 = fn1;
// fn1 = fn3; // 必须一模一样
// fn3 = fn1
*/
// 函数协变
// 参数逆变
/*
let fn1 = (x:(number | string)) => {};
let fn2 = (x:number) => {};
// fn1 = fn2; // 错误:不能将参数类型更具体的函数赋给参数类型更宽泛的函数
fn2 = fn1; // 正确:可以将参数类型更宽泛的函数赋给参数类型更具体的函数
*/
// 返回值双向协变
/*
let fn1 = (x:boolean):(number | string) => x ? 123 : 'abc';
let fn2 = (x:boolean):number => 456;
fn1 = fn2; // 可以将返回值是具体类型的赋值给联合类型
// fn2 = fn1; // 不能将返回值是联合类型的赋值给具体类型的
*/
// 函数重载
function add(x:number, y:number):number;
function add(x:string, y:string):string;
function add(x, y){
return x + y;
}
function sub(x:number, y:number):number;
function sub(x, y){
return x - y;
}
// let fn = add;
// fn = sub; // 不能将重载少的赋值给多的
let fn = sub;
fn = add; // 可以将重载多的赋值给重载少的枚举兼容性
export {}
// 数字枚举与数值兼容
/*
enum Gender{
Male,
Female
}
let value:Gender;
value = Gender.Female;
value = 1;
*/
// 数字枚举与数字枚举不兼容
/*
enum Gender1{
Male,
Female
}
enum Gender2{
Male,
Female
}
let value:Gender1;
value = Gender1.Male;
// value = Gender2.Male; // 不能将类型“Gender2.Male”分配给类型“Gender1”。
*/
// 字符串枚举与字符串枚举不兼容
enum Gender{
Male = '男',
Female = '女'
}
let value:Gender;
value = Gender.Male;
value = '男'; // 不能将类型“"男"”分配给类型“Gender”类兼容性
export {}
// 只比较实例成员,不比较类的构造函数和静态成员
/*
class Person{
public name:string;
public static age:number;
constructor(name:string, age:number){}
}
class Animal{
public name:string;
constructor(name:string){}
}
let p:Person;
let a:Animal;
// p = a;
a = p; // 可多不可少
*/
// 类的私有属性和受保护属性会影响兼容性
class Person {
protected name:string;
}
class Animal {
protected name:string;
}
let p:Person;
let a:Animal;
// p = a;
// a = p;泛型兼容性
// 泛型只影响使用的部分,不会影响声明的部分
interface TestInterface<T> {
age:T; // 用到了范型才会影响到兼容性
}
let t1:TestInterface<number>; // age number
let t2:TestInterface<string>; // age string
// t1 = t2;
// t2 = t1;交叉类型
格式:type1 & type2 & ..
交叉类型是将多个类型合并为一个类型
// 交叉类型
let mergeFn = <T, U>(arg1:T, arg2:U):(T & U) => {
let res = {} as (T & U);
res = Object.assign({}, arg1, arg2);
return res
}
let res = mergeFn({name: '张三'}, {age: 18});
console.log(res);联合类型
格式:type1 | type2 | ..
联合类型是多个类型中的任意一个类型
// 联合类型
let value:(string | number);
value = 123;
value = 'abc';类型保护
对于联合类型的变量,在使用时如何确切的告诉编译器它是哪一种类型
export {}
// 类型保护
let getRandomValue = ():(string | number) => {
let num = Math.random();
return (num >= 0.5) ? 'abc' : 123.123;
}
let value = getRandomValue();
// 通过类型断言可以确切告诉编辑器当前变量是什么类型
// 比较麻烦,冗余代码比较多
/*
if((value as string).length){
console.log((value as string).length);
}else{
console.log((value as number).toFixed(2));
}
*/
// 通过类型保护函数,这个函数返回一个布尔值
/*
function isString(value:string | number):value is string{
return typeof value === 'string';
}
if(isString(value)){
console.log(value.length);
}else{
console.log(value.toFixed());
}
*/
// 除了可以通过类型保护来告诉编译器以外,还可以使用typeof来实现类型保护
// 只能对 number/string/boolean/symbol类型有效
/*
if(typeof value === 'string'){
console.log(value.length);
}else{
console.log(value.toFixed());
}
*/
// 除了通过typeof来判断类型,也可以使用instanceof来判断类型
class Person{
name:string = 'hello';
}
class Animal{
age:number = 18;
}
let getRandomObject = ():(Person | Animal) => {
let num = Math.random();
return (num >= 0.5) ? new Person() : new Animal();
}
let obj = getRandomObject();
if(obj instanceof Person){
console.log(obj.name);
}else{
console.log(obj.age);
}null和undefined
TypeScript具有两种特殊的类型,null和undefined,它们分别具有值null和undefined
默认情况下我们可以将null和undefined赋值给任意类型
默认情况下null和undefined也可以相互赋值
在企业开发总如果不想把null和undefined赋值给其它类型,那我们可以开启strictNullChecks
- 如果开启了strictNullChecks,还想把null和undefined赋值给其它类型
- 我们就必须在声明的时候使用联合类型
- 对于可选属性和可选参数而言,如果开启了strictNullChecks,那么默认情况下数据类型就是联合类型,就是当前的类型+undefined类型
let value:(number | undefined | null);
value = undefined;
value = null;
class Person{
name?:string;
}去除null和undefined检测
// 去除null和undefined检测
function getLength(value:(string | undefined | null)){
return () => {
// return value.length // 报错
// return (value || '').length
// return (value as string).length
return value!.length
}
}
let res = getLength('hello world!');类型别名
类型别名就是给一个类型起一个新名字,但是它们都代表同一个类型
例如:本命叫张三,外号可以叫小三,小三是张三的别名,张三和小三都表示同一个人
// 类型别名
// type MyString = string;
// let value:MyString;
// value = 'hello world';
// 类型别名也可以使用范型
// type MyString<T> = {
// x: T,
// y: T
// }
// let value:MyString<number>;
// value = {x:1,y:2};
// 可以在类型别名中的属性使用自己
// type MyType = {
// name: string,
// children?: MyType
// }
// let value:MyType;
// value = {
// name: '1-0',
// children: {
// name: '2-1'
// }
// }
// 接口和类型别名是互相兼容的
type MyType = {
name: string;
}
interface MyInterface {
name: string;
}
let value1:MyType = {name: 'zs'};
let value2:MyInterface = {name: 'ls'};
// value1 = value2;
value2 = value1;类型别名和接口异同
// 接口和类型别名异同
// 相同点
// 都可以描述属性和方法
// type MyType = {
// name:string;
// say():void;
// }
// interface MyInterface{
// name:string;
// say():void;
// }
// 都允许扩展
// interface MyInterface{
// name:string;
// say():void;
// }
// interface MyInterface2 extends MyInterface{
// age:number;
// }
// let value:MyInterface2 = {
// name:'',
// age:0,
// say(){}
// }
// type MyType = {
// name:string;
// say():void;
// }
// type MyType2 = MyType & {
// age:number;
// }
// let value2:MyType2 = {
// name:'',
// age:0,
// say(){}
// }
// 不同点
// type可以声明基本类型别名,联合类型,元祖等类型,interface不能
type MyType1 = boolean;
type MyType2 = string | number;
type MyType3 = [string,number, boolean];
// type不会自动合并
interface MyInterface{
name:string;
}
interface MyInterface{
age: number
}
let value:MyInterface = {
name: 'zs',
age: 18
}字面量类型
字面量就是源代码中一个固定的值
例如数值字面量:1,2,3 … 例如字符串字面量:‘a’, ‘abc’, …
在TS中我们可以把字面量作为具体的类型来使用
当使用字面量作为具体的类型时,该类型的取值就必须是该字面量的值
// 字面量
type MyNum = 1;
let vlaue1: MyNum = 1;可辨识联合
具有共同的可辨识特征。
一个类型别名,包含了具有共同的可辨识特征的类型的联合。
// 可辨识联合
interface Square{
kind: "square",
size: number
}
interface Rectangle{
kind: "rectangle",
width: number,
height: number
}
interface Circle{
kind: "circle",
radius: number
}
// Shape就是一个可辨识联合
// 它的取值是一个联合,这个联合的取值都有一个共同的可辨识特征
type Shape = (Square | Rectangle | Circle);
function aera(s: Shape){
switch(s.kind){
case "square":
return s.size * s.size;
case "rectangle":
return s.width * s.height;
case "circle":
return Math.PI * s.radius ** 2; // **是ES中推出的幂运算符
}
}可辨识联合完整性检查
在企业开发中如果相对可辨识联合的完整性进行检查,那么我们可以使用
方式一:给函数添加返回值 + 开启strictNullChecks
方式二:添加添加default + never
// 可辨识联合
interface Square{
kind: "square",
size: number
}
interface Rectangle{
kind: "rectangle",
width: number,
height: number
}
interface Circle{
kind: "circle",
radius: number
}
function MyNever(x:never):never{
throw new Error("可辨识联合处理不完整" + x);
}
type Shape = (Square | Rectangle | Circle);
function aera(s: Shape){
switch(s.kind){
case "square":
return s.size * s.size;
case "rectangle":
return s.width * s.height;
case "circle":
return Math.PI * s.radius ** 2; // **是ES中推出的幂运算符
default: return MyNever(s)
}
}索引访问操作符
通过[]索引访问操作符,我们就能得到某个索引的类型
class Person {
name:string;
age:number;
}
type MyType = Person['name'];
// 应用场景
// 需求:获取指定对象,部分属性的值,放到数组中返回
let obj = {
name: 'lnj',
age: 18,
gender: true,
}
function getValues<T, K extends keyof T>(obj:T, keys:K[]):T[K][] {
let arr = [] as T[K][];
keys.forEach(key => {
arr.push(obj[key]);
})
return arr
}
let res = getValues(obj, ['name', 'age']);
// 索引访问操作符注意点
// 不会返回null/undefined/never类型
interface TestInterface{
a:string,
b:number,
c:boolean,
d:symbol,
e:null,
f:undefined,
g:never
}
type MyType2 = TestInterface[keyof TestInterface]映射类型
根据旧的类型创建出新的类型,称之为映射类型
Readonly
复制后的类型为只读属性
// 映射类型
// Readonly
// Partial
interface TestInterface {
name:string,
age:number,
}
type MyType = Readonly<TestInterface>; // 只读
type MyType2 = Partial<TestInterface>; // 可选
type MyType3 = Readonly<Partial<TestInterface>> // 只读 + 可选Partial
复制后的属性为可选属性
// 映射类型
// Partial
interface TestInterface {
name:string,
age:number,
}
type MyType = Readonly<TestInterface>; // 只读
type MyType2 = Partial<TestInterface>; // 可选
type MyType3 = Readonly<Partial<TestInterface>> // 只读 + 可选Pick
将原有的类型中的部分内容映射到新的类型中
// Pick
interface TestInterface {
name:string,
age:number,
}
type MyType = Pick<TestInterface, "name">; // {name:string}Record
将一个类型的属性值都映射到另一个类型上并创造一个新的类型
// Record
type Animal = 'person' | 'dog' | 'cat';
interface TestInterface {
name:string,
age:number,
}
type MyType = Record<Animal, TestInterface>;
let res:MyType = {
person: {
name: 'zs',
age: 18
},
dog: {
name: 'ls',
age: 18
},
cat: {
name: 'ww',
age: 18
}
}映射类型进行推断
对于Readonly,Partial和Pick的映射类型,我们可以对映射之后的类型进行拆包
还原映射之前的类型,这种操作我们称之为拆包
interface MyInterface{
name:string,
age:number
}
type MyType<T> = {
+readonly [P in keyof T]: T[P];
}
type test = MyType<MyInterface>;
type UnMyType<T> = {
-readonly [P in keyof T]: T[P];
}
type test2 = UnMyType<test>;条件类型(三目运算)
判断前面一个类型是否跟后面一个类型或者继承于后面一个类型
如果是就返回一个结果,如果不是就返回第二个结果
语法:T extends U ? X : Y
// 条件类型(三目运算)
type MyType<T> = T extends string ? T : any;
type res = MyType<boolean>;分布式条件类型
被检测类型是一个联合类型的时候,该条件类型就称之为分布式条件类型
type MyType<T> = T extends any ? T : never;
type res = MyType<string | number | boolean>;Exclude
// 从T中剔除可以复制给U的类型 Exclude
type res = Exclude<string | number | boolean, number>; // type res = string | numberExtract
// 提取T中可以复制给U的类型 Extract
type res = Extract<string | number | boolean, number>; // type res = number;NonNullable
// 从T中剔除Null和undefined NonNullable
type res = NonNullable<string | null | undefined | number>; // type res = string | numberReturnType
// 获取函数返回值类型 ReturnType
type res = ReturnType<(() => string)> // type res = stringConstructorParameters
// 获取一个类的构造函数组成的元祖类型。 ConstructorParameters
class Person{
constructor(name:string, age:number) {}
}
type res = ConstructorParameters<typeof Person>; // type res = [name:string, age:number]Parameters
// 获得函数的参数类型组成的元祖类型。 Parameters
function say(name:string, age:number, gender:boolean){}
type res = Parameters<typeof say>; // type res = [name:string, age:number, gender:boolean];infer
// infer关键字
// 条件类型提供了一个infer关键字,可以让我们在条件类型中定义新的类型
// 需求:定义一个类型,如果传入的是数组,就返回数组的元素类型,如果传入的是普通类型,则直接返回这个类型
// type MyType<T> = T extends any[] ? T[number] : T;
// type res = MyType<string[]>; // type res = string;
// type res = MyType<number>; // type res = number;
type MyType<T> = T extends Array<infer U> ? U : T;
// type res = MyType<string[]>; // type res = string;
type res = MyType<number>; // type res = number;unknown
// unknown
// unknown类型是TS3.0中新增的一个类型,被称作安全的any
// 1.任何类型都可以复制给unknown类型
// let value:unknown;
// value = 123;
// value = 'abc';
// value = true;
// 2.如果没有类型断言或基于控制流的类型细化,那么不能将unknown类型赋值给其它类型
// let value1:unknown = 123;
// let value2:number;
// value2 = value1 as number;
// if(typeof value1 === "number") {
// value2 = value1;
// }
// 3.如果没有类型断言或基于控制流的类型细化,那么不能在unknown类型上进行任何操作
// let value1:unknown = 123;
// (value1 as number)++
// if(typeof value1 === 'number'){
// value1++
// }
// 4.只能对unknown类型进行 相等或不等操作,不能进行其它操作(因为其它操作没有意义)
// let value1:unknown = 123;
// let value2:unknown = 123;
// console.log(value1 === value2);
// console.log(value1 !== value2);
// 5.unknown与任何其它类型组成的交叉类型最后都是其它类型
// type MyType = unknown & number; // type MyType = number;
// type MyType2 = string & unknown; // type MyType2 = string;
// 6.unknown除了与any以外,与其它任意类型组成的联合类型最后都是unknown类型
// type MyType = number | unknown; // type MyType = unknown;
// type MyType2 = unknown | string; // type MyType = unknown;
// 7.never类型是unknown类型的子类型
// type MyType = never extends unknown ? true : false; // true;
// 8.keyof unknown等于never
// type MyType = keyof unknown; // type MyType = never;
// 9.unknown类型的值不能访问属性,方法,创建实例
// class Person{
// name: string = 'zs';
// say():void{
// console.log(`name=${this.name}`);
// }
// }
// let p:unknown = new Person();
// console.log(p.name); // Property name does not exist on type unknown
// p.say(); // Property name does not exist on type unknown
// 10.使用映射类型时,如果遍历的是unknown类型,那么不会映射任何属性
type MyType<T> = {
[P in keyof T]: any;
}
type res = MyType<unknown>; // type res = {}模块系统
// 60-index.ts
// ES6的模块和Node的模块是不兼容的,所以TS为了兼容两者就推出了
// export = xxx;
// import xxx = require('path');
import obj = require('./61-index');
console.log(obj);
// 61-index.ts
export const obj = {
name: 'zs',
age: 20
}