TypeScript

1、TypeScript开发环境搭建

1、下载Node.js

2、安装Node.js

3、使用npm全局安装typescript

①、进入命令行

②、输入:npm i -g typescript

4、创建一个ts文件

5、使用tsc对他说文件进行编译

①、进入命令行

②、进入ts文件所在目录

③、执行命令:tsc xxx.ts

2、基本类型

类型声明:

1、类型声明是TS非常重要的一个特点

2、通过类型声明可以指定TS中变量(参数、形参)的类型

3、指定类型后,当为变量赋值时,TS编译器会自动检查值是否符合类型声明,符合则赋值,否则报错

4、类型声明给变量设置了类型,使得变量之恶能存储某种类型的值

语法:

let 变量: 类型;
let 变量: 类型 = 值;
function fn(参数:类型,参数:类型):类型{
    ...
}

实例:

let a: number = 10//指定a变量类型为number

// 如果变量的声明和赋值是同时进行的,TS可以自动对变量进行类型检测
let c = true;
// 为函数参数进行变量指定
function sum(a:number,b:number):number{//指定函数返回值
    return a+b;
}
let res = sum(123,456);//只能传入两个参数,不能多传或少传

注意:

  • 1、当对变量的声明和赋值时同时进行的,TS编译器会自动判断变量的类型
  • 2、如果你的变量的声明是同时进行的,可以省略掉类型声明

类型:

类型

例子

描述

number

1,2,3

任意数字

string

‘hi’,“hi”

任意字符串

boolean

true,false

布尔值true或false

字面量

其本身

限制变量的值就是该字面量的值

any

*

任意类型

unknown

*

类型安全的any

void

空值(undefined)

没有值(或undefined)

never

没有值

不能是任何值

object

{name:‘张三’}

任意的JS对象

array

[1,2,3]

任意JS数组

tuple

[4,5]

元组,TS新增类型,固定长度数组

enum

enum{A,B}

枚举,TS中新增类型

字面量:

限制变量的值就是该字面量的值

let b:"male"|"female"
b = "male",
b = "female"
//可以指定多个变量类型
let c:boolean | string
c = true;
c = 'hello';
let str1 = 'Hello TS'
const str2 = 'HELLO TS'

通过TS类型推论机制:

  • 1、str1的类型为:string
  • 2、str2的类型为:‘HELLO TS’

解释:str2是一个常量,它的值不能变化只能是"HELLO TS", 所以它的类型为:‘HELLO TS’。此处就为一个字面量类型。也就是说某个特定的字符串也可以作为TS中的类型

**使用模式:**字面量类型配合联合类型一起使用

**使用场景:**用来表示一组明确的可选值列表

比如:在贪吃蛇游戏中,游戏的方向的可选值只能是上、下、左、右中的任意一个

function changeDirection(direction:'up'|'down'|'left'|'right'){
    console.log(direction)
}

解释:参数的direction的值只能是 up/down/left/right中的任意一个

优势:相比于string类型,使用字面量类型更加精确、严谨

any:

1、any表示的是任意类型,一个变量设置类型为any后相当于该变量关闭了TS的类型检测

2、声明变量如果不指定类型,则TS解析器会自动判断变量的类型为any(隐式的any)函数参数不设置类型也会隐式具有any类型

let d;
d = 10;
d = 'hello';

3、any的类型可以赋值给任意变量

let d:any;
let s:string;
s = d;

unknown:

表示未知类型的值(遇到类型不确定的变量时尽量用unknown)

实际上就是一个类型安全的any,不能直接赋值给其他变量

//需要做类型判断才能赋值
let s:unknown = 'hello';
let e:string = 'world';
if(typeof e === "string"){
    s = e;
}
// 类型断言,可以用来告诉解析器变量的实际类型
//语法:1、变量 as 变量
//	   2、<类型>变量
s = e as string
s = <string>e;

void:

函数默认返回值为void,但可以根据return的值自动判断类型,

如果需要指定函数没有返回值时,指定返回值类型为void

void用来表示空,以函数为例,就表示没有返回值的函数

// void
function fn():void{
    
}

never:

// never表示永远不会返回结果
function fn2():never{
    throw new Error('报错')
}

object:(包含数组、对象、函数等对象)

特点:对象类型,在TS中更加细化,每个具体的对象都有自己的类型语法

表示一个js对象

{}用来指定对象中可以包含哪些属性

语法:{属性名:属性值,属性名:属性值}

// object 表示一个js对象
let f:object;
f = {};

let g:{name:string};

g = {name:'lisi'}

Number:

Number对象属性:

属性

描述

MAX_VALUE

可表示的最大的数,MAX_VALUE 属性值接近于 1.79E+308。大于 MAX_VALUE 的值代表 “Infinity”。

MIN_VALUE

可表示的最小的数,即最接近 0 的正数 (实际上不会变成 0)。最大的负数是 -MIN_VALUE,MIN_VALUE 的值约为 5e-324。小于 MIN_VALUE (“underflow values”) 的值将会转换为 0。

NaN

非数字值(Not-A-Number)。

NEGATIVE_INFINITY

负无穷大,溢出时返回该值。该值小于 MIN_VALUE。

POSITIVE_INFINITY

正无穷大,溢出时返回该值。该值大于 MAX_VALUE。

prototype

Number 对象的静态属性。使您有能力向对象添加属性和方法。

constructor

返回对创建此对象的 Number 函数的引用。

Number对象方法:

String:

String对象属性:

属性

描述

constructor

对创建该对象的函数的引用。

length

返回字符串的长度。

prototype

允许您向对象添加属性和方法。

String方法:

方法

描述

charAt()

返回在指定位置的字符。

charCodeAt()

返回在指定的位置的字符的 Unicode 编码。

concat()

连接两个或更多字符串,并返回新的字符串。

indexOf()

返回某个指定的字符串值在字符串中首次出现的位置。

lastIndexOf()

从后向前搜索字符串,并从起始位置(0)开始计算返回字符串最后出现的位置。

localeCompare()

用本地特定的顺序来比较两个字符串。

match()

查找找到一个或多个正则表达式的匹配。

replace()

替换与正则表达式匹配的子串

search()

检索与正则表达式相匹配的值

slice()

提取字符串的片断,并在新的字符串中返回被提取的部分。

split()

把字符串分割为子字符串数组。

substr()

从起始索引号提取字符串中指定数目的字符。

substring()

提取字符串中两个指定的索引号之间的字符。

toLocaleLowerCase()

根据主机的语言环境把字符串转换为小写,只有几种语言(如土耳其语)具有地方特有的大小写映射。

toLocaleUpperCase()

据主机的语言环境把字符串转换为大写,只有几种语言(如土耳其语)具有地方特有的大小写映射。

toLowerCase()

把字符串转换为小写。

toString()

返回字符串。

toUpperCase()

把字符串转换为大写。

valueOf()

返回指定字符串对象的原始值。

数组:

数组的类型声明:

类型[ ];

数组<类型>

//string[] 表示字符串数组
let e: string[];
e = ['a','b','c'];
//number[]表示数值数组
let f:number[] = [1,2,3];
let g:Array<number> = [1,2,3];
//需求,数组中既有number类型,又有string类型
let arr: (number|string)[] = [1,'a',3,'b']

注:| 在TS中叫做联合类型(由两个或多个其他类型组成的类型,表示可以是这些类型中的任意一种)

数组解构

将数组元素赋值给变量

var arr:number[] = [12,13] 
var[x,y] = arr // 将数组的两个元素赋值给变量 x 和 y
console.log(x) 
console.log(y)

数组迭代

var j:any; 
var nums:number[] = [1001,1002,1003,1004] 
 
for(j in nums) { 
    console.log(nums[j]) 
}

多维数组

一个数组的元素可以是另外一个数组,这样构成了多维数组。

var multi:number[][] = [[1,2,3],[23,24,25]]  
console.log(multi[0][0]) 
console.log(multi[0][1]) 
console.log(multi[0][2]) 
console.log(multi[1][0]) 
console.log(multi[1][1]) 
console.log(multi[1][2])

数组方法:

方法

描述

concat()

连接两个或更多的数组,并返回结果。

every()

检测数值元素的每个元素是否都符合条件。

filter()

检测数值元素,并返回符合条件所有元素的数组

forEach()

数组每个元素都执行一次回调函数。

indexOf()

搜索数组中的元素,并返回它所在的位置。如果搜索不到,返回值 -1,代表没有此项。

join()

把数组的所有元素放入一个字符串。

lastIndexOf()

返回一个指定的字符串值最后出现的位置,在一个字符串中的指定位置从后向前搜索。

map()

通过指定函数处理数组的每个元素,并返回处理后的数组。

pop()

删除数组的最后一个元素并返回删除的元素。

push()

向数组的末尾添加一个或更多元素,并返回新的长度。

reduce()

将数组元素计算为一个值(从左到右)。

reduceRight()

将数组元素计算为一个值(从右到左)。

reverse()

反转数组的元素顺序。

shift()

删除并返回数组的第一个元素。

slice()

选取数组的的一部分,并返回一个新数组。

some()

检测数组元素中是否有元素符合指定条件。

sort()

对数组的元素进行排序。

splice()

从数组中添加或删除元素。

toString()

把数组转换为字符串,并返回结果。

unshift()

向数组的开头添加一个或更多元素,并返回新的长度。

元组:

元组类型是另一种类型的数组,它确切地直到包含多少个元素以及特定索引对应的类型

语法:[类型,类型]

let h:[string,string];
h = ['hello','world']

let position:[number,number] = [39.1,39.2]

解释:

  1. 元组类型可以确切地标记出有多少个元素,以及每个元素的类型
  2. 该示例中,元组有两个元素,每个元素的类型都是number

enum:

枚举的功能类似于字面量类型+联合类型组合的功能,也可以表示一组明确的可选值

枚举:定义一组命名常量。它描述一个值,该值可以是这些命名常量中的一个

enum Gender{
    Male = 0,
    Female = 1
}

let i:{name:string,gender:Gender}
i = {
    name:'李四',
    gender:Gender.Male
}
//console.log(i.gender === Gender.Male)
let j: {name:string} & {age:number};//&表示同时
j = {name:'李四',age:18}

解释:1、使用enum关键字定义枚举

2、约定枚举名称、枚举中的值以大写字母开头

3、枚举中的多个值之间通过 , 分隔

4、定义好枚举后,直接使用枚举名称作为类型注解

注意:1、枚举成员是有值的,默认为:从0开始自增的值

2、字符串枚举没有自增长行为,因此字符串枚举中必须设置初始化值

说明:枚举与字面量类型+联合类型组合的功能类似,都用来表示一组明确的可选值列表

Map对象:

Map 对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者原始值) 都可以作为一个键或一个值。

创建map

let myMap = new Map();
let myMap = new Map([
  ["key1","value1"],
  ["key2","value2"]
])

Map 相关的函数与属性:

  • map.clear() – 移除 Map 对象的所有键/值对 。
  • map.set() – 设置键值对,返回该 Map 对象。
  • map.get() – 返回键对应的值,如果不存在,则返回 undefined。
  • map.has() – 返回一个布尔值,用于判断 Map 中是否包含键对应的值。
  • map.delete() – 删除 Map 中的元素,删除成功返回 true,失败返回 false。
  • map.size – 返回 Map 对象键/值对的数量。
  • map.keys() - 返回一个 Iterator 对象, 包含了 Map 对象中每个元素的键 。
  • map.values() – 返回一个新的Iterator对象,包含了Map对象中每个元素的值 。

迭代Map

Map 对象中的元素是按顺序插入的,我们可以迭代 Map 对象,每一次迭代返回 [key, value] 数组。

TypeScript使用for...of来实现迭代:

let nameSiteMapping = new Map();
 
nameSiteMapping.set("Google", 1);
nameSiteMapping.set("Runoob", 2);
nameSiteMapping.set("Taobao", 3);
 
// 迭代 Map 中的 key
for (let key of nameSiteMapping.keys()) {
    console.log(key);                  
}
 
// 迭代 Map 中的 value
for (let value of nameSiteMapping.values()) {
    console.log(value);                 
}
 
// 迭代 Map 中的 key => value
for (let entry of nameSiteMapping.entries()) {
    console.log(entry[0], entry[1]);   
}
 
// 使用对象解析
for (let [key, value] of nameSiteMapping) {
    console.log(key, value);            
}

类型的别名:

类型别名(自定义类型):为任意类型起别名

使用场景:当同一类型(复杂)被多次使用时,可以通过类型别名,简化该类型的使用

type myType = 1|2|3|4|5|6
let k:myType

解释:1、使用type关键字来创建类型别名

2、类型别名(比如,此处的CustomArray),可以是任何合法的变量名称

3、创建类型别名后,直接使用该类型别名作为变量的类型注解即可

函数类型:

函数的类型实际上指的是:函数参数和返回值的类型

为函数指定类型的两种方式:

1、单独指定参数、返回值的类型

function add(num1:number,num2:number):number{
    return num1+num2
}
const add = (num1:number,num2:number):number=>{
    return num1 + num2
}

2、同时指定参数、返回值的类型

const add:(num1:number,num2:number)=> number = (num1,num2)=>{
    return num1 + num2
}

解释:当函数作为表达式时,可以通过类似箭头函数形式的语法来为函数添加类型。

注意:这种形式只适用于函数表达式

在 TypeScript 函数里,如果我们定义了参数,则我们必须传入这些参数,除非将这些参数设置为可选,可选参数使用问号标识 ?。

3、使用函数实现某个功能时,参数可以传也可以不传。这种情况下,在给函数参数指定类型时,就用到可选参数了。比如:数组的sice方法()

function mySlice(start?:number,end?:number):void{
    console.log('起始索引',start,'结束索引',end)
}

可选参数:在可传可不传的参数名称后面添加?(问号)

注意:可选参数只能出现在参数列表的最后,也就是说可选参数后面不能再出现必选参数

4、默认参数,可以设置参数的默认值,这样在调用函数的时候,如果不传入该参数的值,则使用默认参数,语法格式为:

function calculate_discount(price:number,rate:number = 0.50) { 
    var discount = price * rate; 
    console.log("计算结果: ",discount); 
} 
calculate_discount(1000) 
calculate_discount(1000,0.30)

注意:参数不能同时设置为可选和默认。

5、剩余参数,当不知道要向函数中传入多少个参数,这时候可以使用剩余参数来定义。

//函数的最后一个命名参数 restOfName 以 ... 为前缀,它将成为一个由剩余参数组成的数组,索引值从0(包括)到 restOfName.length(不包括)。
function buildName(firstName: string, ...restOfName: string[]) {
    return firstName + " " + restOfName.join(" ");
}
  
let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");

6、匿名函数,匿名函数是一个没有函数名的函数,匿名函数在程序运行时动态声明,且可以将匿名函数赋值给一个变量。

var msg = function() { 
    return "hello world";  
} 
console.log(msg())
//
var res = function(a:number,b:number) { 
    return a*b;  
}; 
console.log(res(12,2))

**匿名函数自调用:**匿名函数自调用在函数后使用 ()

(function(){
  var x = "a";
  console.log(x)
})()

7、构造函数:TypeScript 也支持使用 JavaScript 内置的构造函数 Function() 来定义函数

var res = new Function ([arg1[, arg2[, ...argN]],] functionBody)
//参数说明:

//arg1, arg2, ... argN:参数列表。
//functionBody:一个含有包括函数定义的 JavaScript 语句的字符串。
var myFunction = new Function("a", "b", "return a * b"); 
var x = myFunction(4, 3); 
console.log(x);

8、递归函数:递归函数即在函数内调用函数本身

function factorial(number) {
    if (number <= 0) {         // 停止执行
        return 1; 
    } else {     
        return (number * factorial(number - 1));     // 调用自身
    } 
}; 
console.log(factorial(6));      // 输出 720

9、Lambda函数,也称为箭头函数。

var foo = (x:number)=>10+x
console.log(foo(100))      //输出结果为 110
//
var foo = (x:number)=> {    
    x = 10 + x 
    console.log(x)  
} 
foo(100)
//可以不指定函数的参数类型,通过函数内来推断参数类型
var func = (x)=> { 
    if(typeof x=="number") { 
        console.log(x+" 是一个数字") 
    } else if(typeof x=="string") { 
        console.log(x+" 是一个字符串") 
    }  
} 
func(12) 
func("Tom")

函数重载:重载时方法名字相同,而参数不同,返回类型也可以相同也可以不同,每个重载的方法都必须有一个独一无二的参数类型列表

//参数类型不同
function disp(string):void; 
function disp(number):void;
//参数数量不同
function disp(n1:number):void; 
function disp(x:number,y:number):void;
//参数类型顺序不同
function disp(n1:number,s1:string):void; 
function disp(s:string,n:number):void;

如果参数类型不同,则参数类型应设置为 any

参数数量不同你可以将不同的参数设置为可选。

实例:

function disp(s1:string):void; 
function disp(n1:number,s1:string):void; 
function disp(x:any,y?any):void{
  	console.log(x); 
    console.log(y);
}
disp("abc") 
disp(1,"xyz");

对象类型:

JS中的对象是由属性和方法构成的,而TS中对象的类型就是在描述对象的结构(有什么类型的属性和方法)

对象类型的写法:

let person:{name:string; age:number; sayHi(name:string):void} = {
    name:'jack',
    age:19,
    sayHi(name){}
}

解释:1、直接使用{} 来描述对象结构。属性采用属性名:类型 的形式;方法采用方法名():返回值类型的形式

2、如果方法有参数,就在方法名后面的小括号中指定参数类型(比如:greet(name:string):void)

3、在一行代码中指定对象的多个类型属性时,使用 ; 来分隔

可选属性:

对象的属性或方法,也可以是可选的,此时就用到可选属性

function myAxios(config:{url:string;method?:string}){
    console.log(config)
}

可选属性的语法与函数可选参数的语法一致,都使用 ? (问号)来表示

鸭子类型

鸭子类型(英语:duck typing)是动态类型的一种风格,是多态(polymorphism)的一种形式。

在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由"当前方法和属性的集合"决定。

在鸭子类型中,关注点在于对象的行为能做什么,而不是关注对象所属的类型。例如,在不使用鸭子类型的语言中,我们可以编写一个函数,它接受一个类型为"鸭子"的对象,并调用它的"走"和"叫"方法。在使用鸭子类型的语言中,这样的一个函数可以接受一个任意类型的对象,并调用它的"走"和"叫"方法。如果这些需要被调用的方法不存在,那么将引发一个运行时错误。任何拥有这样的正确的"走"和"叫"方法的对象都可被函数接受的这种行为引出了以上表述,这种决定类型的方式因此得名。

interface IPoint { 
    x:number 
    y:number 
} 
function addPoints(p1:IPoint,p2:IPoint):IPoint { 
    var x = p1.x + p2.x 
    var y = p1.y + p2.y 
    return {x:x,y:y} 
} 
 
// 正确
var newPoint = addPoints({x:3,y:4},{x:5,y:1})  
 
// 错误 
var newPoint2 = addPoints({x:1},{x:4,y:3})

接口:

接口是一系列抽象方法的声明,是一些方法特征的集合,这些方法都应该是抽象的,需要由具体的类去实现,然后第三方就可以通过这组抽象方法调用,让具体的类执行具体的方法。

当一个对象类型被多次使用时,一般会使用接口(interface)来描述对象的类型,达到复用的目的

解释:1、使用interface关键字来声明接口

2、接口名称(比如,此处的IPerson),可以是任何合法的变量名称

3、声明接口后,直接使用接口名称作为变量的类型

4、因为每一行只有一个属性类型,因此,属性类型后没有 ;

interface IPerson{
    name:string
    age:number
    sayHi():void
}
let person:IPerson = {
    name:'jack',
    age:19,
    sayHi(){}
}

interface(接口)和type(类型别名)的对比:

相同点:都可以给对象指定类型

不同点:

  1. 接口,只能为对象指定类型
  2. 类型别名,不仅可以为对象指定类型,实际上可以为任意类型指定别名。
//接口
interface IPerson{
    name:string
    age:number
    sayHi():void
}
//类型别名
type IPerson = {
    name:string
    age:number
    sayHi():void
}
type NumStr = number | string

接口的继承:

如果两个接口之间有相同的属性或方法,可以将公共的属性或方法抽离出来,通过继承来实现复用。

比如:

interface Point2D{x:number;y:number}
interface Point3D{x:number;y:number;z:number}
//更好的方法
interface Point2D{x:number;y:numer}
interface Point3D extends Ponit2D{z:number}

解释:

  1. 使用extends(继承)关键字实现了接口Point3D继承Point2D
  2. 继承后,Point3D就有了Point2D的所有属性和方法(此时,Point3D同时有x、y、z、三个属性)

类型断言

类型断言用来指定更具体的类型

<a href="" id="link"></a>
const aLink = document.getElementById('link') as HTMLAnchorElement

解释:

  1. 使用as关键字实现类型断言
  2. 关键字as后面的类型是一个更加具体的类型(HTMLAnchorElement是HTMLElement的子类型)
  3. 通过类型断言,alink的类型变得更加具体,这样就可以访问a变迁特有的属性或方法了
//另一种语法
const alink = <HTMLAnchorElement>document.getElementById('link')

TypeScript联合类型

联合类型(Union Types)可以通过管道(|)将变量设置多种类型,赋值时可以根据设置的类型来赋值。

注意:只能赋值指定的类型,如果赋值其它类型就会报错。

创建联合类型的语法格式如下:

Type1|Type2|Type3

typeof操作符

TS提供typeof操作符:可以在类型上下文中引用变量或属性的类型(类型查询)

使用场景:根据已有变量的值,获取该值的类型,来简化类型书写

let p = {x:1,y:2}
function formatPoint(point:{x:number;y:number}){}
formatPoint(p)
function formatPoint(point:typeof p){}

解释:1、使用typeof操作符来获取变量p的类型,结果与第一种(对象字面量形式的类型)相同

2、typeof出现在类型注解的位置(参数名称的冒号后面)所处的环境就在类型上下文(区别于JS代码)

3、注意:typeof只能用来查询变量或属性的类型,无法查询其他形式的类型(比如,函数调用的类型)

3、高级类型

class类

TS全面支持ES2015中引入的class关键字,并为其添加了类型注解和其他语法

class的基本使用:

class Person{}
const p = new Person()

解释:

  1. 根据TS中的类型推论,可以知道Person类的实例对象p的类型是Person
  2. TS中的class,不仅提供了class的语法功能,也作为一种类型存在

类的构造函数:

class Person{
    age:number
    gender:string
    
    constructor(age:number,gender:string){
        this.age = age
        this.gender = gender
    }
}

解释:

  1. 成员初始化后,才可以通过this.age来访问实例成员
  2. 需要为构造函数指定类型注解,否则会被隐式推断为any;构造函数不需要返回值类型

类的实例方法:

class Point{
    x = 10
    y = 10
    scale(n:number):void{
        this.x *= n
        this.y *= n
    }
}

解释:方法的类型注解()参数和返回值与函数用法相同

类的继承:

1、extends(继承父类)

说明:JS中只有extends,而implements是TS提供的

class Animal{
    move(){
        console.log('Moving along !')
    }
}
class Dog extends Animal{
    bark(){
        console.log('wang')
    }
}
const dog = new Dog()
dog.bark
dog.move

解释:

  1. 通过extends关键字实现继承
  2. 子类Dog继承父类Animal,则Dog的实例对象dog就同时具有了父类Animal和子类Dog的所有属性和方法

2、implements(实现接口)

interface Singable{
    sing():void
}
class Person implements Singable{
    sing(){
        console.log('小苹果')
    }
}

解释:

  1. 通过implements关键字让class实现接口
  2. Person类实现接口Singable意味着,Person类中必须提供Singable接口中指定的所有方法和属性

类成员的可见性 :

可以使用TS来控制class的方法或属性对于class外的代码是否可见

可见性修饰符包括:

  • public(公有的)
//public 表示公开的、公有的,公有成员可以被任何地方访问,默认可见性
class Animal{
    public move(){
        console,log('Moving along!')
    }
}
//解释:1、在类属性或方法前加public关键字,来修饰该属性或方法是公有的
	// 2、因为public是默认可见性,所以可以直接省略
  • protected(受保护的)
//protected 表示受保护的,仅对其声明所在类的子类中(非实例对象)可见
class Animal{
    protected move(){
        console,log('Moving along!')
    }
}
class Dog extends Animal{
    bark(){
        console.loh('wang')
        this.move()
    }
}
//解释:1、在类属性或方法前面添加protected关键字,来修饰该属性或方法是受保护的
//2、在子类的方法内部可以通过this来访问父类中受保护的成员,但是对实例不可见
  • private(私有的)
//private:表示私有的,只在当前类中可见,对实例对象以及子类也是不可见的
class Animal{
    private move(){
        console,log('Moving along!')
    }
    walk(){
        this.move()
    }
}
//解释:1、在类属性或方法前面添加private关键字,来修饰该属性或方法是私有的
//	   2、私有的属性或方法只在当前类中可见,对子类和实例对象也都是不可见的

static关键字

static 关键字用于定义类的数据成员(属性和方法)为静态的,静态成员可以直接通过类名调用。

class StaticMem {  
   static num:number; 
   
   static disp():void { 
      console.log("num 值为 "+ StaticMem.num) 
   } 
} 
 
StaticMem.num = 12     // 初始化静态变量
StaticMem.disp()       // 调用静态方法

instanceof运算符

instanceof 运算符用于判断对象是否是指定的类型,如果是返回 true,否则返回 false。

class Person{ } 
var obj = new Person() 
var isPerson = obj instanceof Person; 
console.log("obj 对象是 Person 类实例化来的吗? " + isPerson);

readonly(只读修饰符):

readonly:表示只读,用来防止在构造函数之外对属性进行赋值

class Person{
    readonly age:number = 18
    constructor(age:number){
        this.age = age
    }
}

解释:

  1. 使用readonly关键字修饰该属性是只读的,(只能修饰属性不能修饰方法)
  2. 注意:属性age后面的类型注解(比如,此处的number)如果不加,则age的类型为18**(字面量类型)**。
  3. 接口或者 {} 表示的对象类型,也可以使用readonly。

类型兼容性

两种类型系统: 1 StructuralType System(结构化类型系统)2Nominal Type System(标明类型系统)。TS采用的是结构化类型系统,也叫做duck typing (鸭子类型),类型检查关注的是值所具有的形状。

也就是说,在结构类型系统中,如果两个对象具有相同的形状,则认为它们属于同一类型。

class Point {x:number;y:number}
class Point2D{x:number;y:numer}

const p: Point = new Point2D()

解释:1、Point和Point2D是两个名称不同的类

2、变量p的类型被显示标注为Point类型,但是,它的值却是Point2D的实例,并且没有类型错误

3、因为TS是结构化类型系统,只检查Point和Point2D的结构是否相同(属性数量和属性类型)

4、但是,如果在Normal Type System中(比如:c#、java),他们就是不同的类,类型无法兼容

更准确的说法:对于对象类型来说,y的成员至少与x相同,则x兼容y(成员多的可以赋值给少的)

class Point {x:number;y:number}
class Point3D{x:number;y:number;z:number}
const p: Point = new Point3D()

解释:1、Point3D的成员至少与POint相同,则Point兼容Point3D

2、所以,成员多的Point3D可以赋值给成员少的Point

接口兼容性:

interface Point{x:number;y:number}
interface Point2D {x:number;y:number}
let p1:Point
let p2:Point2D = p1
interface Point3D {x:number;y:number; z:number}
let p3:Point3D
p2 = p3
//类和接口也是兼容的
class Point4D{
    x:number
    y:number
    z:number
}
p2 = new Point4D()

函数兼容性:

函数之间兼容性比较复杂,需要考虑:

1、参数个数 ,参数多的兼容参数少的(参数少的可以赋值给参数多的

type F1 = (a:number) => void
type F2 = (a:number,b:number) => void
let f1:F1
let f2:F2 = f1

解释:1、参数少的可以赋值给参数多的,所以f1可以赋值给f2

2、数组forEach方法的第一个参数是回调函数

3、在JS中省略用不到的参数实际上是很常见的,这样的使用方式,促成了TS中函数类型之间的兼容性

4、并且因为回调函数是有类型的,所以,TS会自动推导出参数item、index、array的类型

2、参数类型,相同位置的参数类型要相同(原始类型)或兼容(对象类型)

type F1 = (a:number) => string
type F2 = (a:number) => string
let f1:F1
let f2:F2 = f1
//解释:函数类型F2兼容函数类型F1,因为F
interface Point2D{x:number;y:number}
interface Point3D{x:number;y:number;z:number}
type F2 = (p:Point2D)=>void//相当于有两个参数
type F3 = (p:Point3D)=>void//相当于有三个参数
let f2:F2
let f3:F3 = f2
//解释:1、注意,此处与前面讲到的接口兼容性冲突
//	   2、技巧:将对象拆开,把每个属性看做一个个参数,则,参数少的 f2 可以赋值给参数多的f3

3、返回值类型,只关注返回值类型本身即可

type F5 = ()=>string
type F6 = ()=>string
let f5:F5
let f6:F6 = f5
type F7 = ()=>{name:string}
type F8 = ()=>{name:string; age:number}
let f7: F7
let f8: F8
f7 = f8

解释:1、如果返回值类型是原始类型,此时两个类型要相同,比如,F5和F6

2、如果返回值类型是对象类型,此时成员多的可以赋值给成员少的

交叉类型

交叉类型(&):功能类似于接口继承(extends),用于组合多个类型为一个类型(常用于对象类型)

interface Person {name:string}
interface Contact {phone:string}
type PersonDetail = Person & Contact
let obj: PersonDetail = {
    name:'jack',
    phone:'133'
}

解释:使用交叉类型后,新的类型PersonDetail就同时具备了Person和Contact的所有属性类型

交叉类型(&)和接口继承(extends)的对比:

相同点:都可以实现对象类型的组合

不同点:两种方式实现类型组合时,对于同名属性之间,处理类型冲突的方式不同

interface A{
    fn:(value:number) => string
}
interface B extends A {//此处B处会报错(类型不兼容)
    fn:(value:String)=>string
}

interface C {
    fn:(value:string)=>string
}
type D = A & C;//此处不会报错
//可以简单理解为:
fn:(value:string | number)=>string

泛型和keyof

泛型是可以在保证类型安全前提下,让函数等与多种类型一起工作,从而实现复用,常用于:函数、接口、class中

需求:创建一个id函数,传入什么数据就返回该函数本身(也就是说,参数和返回值类型相同)

//创建泛型函数:
function id<Type>(value:Type):Type{return value}

//调用泛型函数
const num = id<number>(10)

解释:1、语法:在函数名称的后面添加<>(尖括号),尖括号中添加类型变量,比如此处的Type

2、类型变量Type,是一种特殊类型的变量,它处理类型而不是值

3、该类型变量相当于一个类型容器,能够捕获用户提供的类型(具体是什么类型由用户调用该函数时指定)

4、因为Type是类型,因此可以将其作为函数参数和返回值的类型,表示参数和返回值具有相同的类型

5、类型变量Type,可以是任意合法的变量名称

泛型在保证类型 安全(不丢失类型信息)的同时,可以让函数等于多种不同的类型一起工作,灵活可复用。

简化调用泛型函数:

function id<Type>(value:Type):Type{
    return value
}
let num = id(10)

解释:1、在调用泛型函数时,可以省略<类型>来简化泛型函数的调用

2、此时,TS内部会采用一种叫做类型参数推断的机制,来根据传入的实参自动推断出类型变量Type的类型

3、比如,传入实参10,TS会自动推断出变量num的类型number,并作为Type的类型

泛型约束:

默认情况下,泛型函数的类型变量Type可以代表多个类型,这导致无法访问任何属性。比如,id(‘a’)调用函数时获取参数的长度:

function id<Type>(value:Type):Type{
    console.log(value.length)
    return value
}

解释:Type可以代表任意类型,无法保证一定存在length属性,比如number类型就没有length。此时,就需要为泛型添加约束来收缩类型(缩窄类型取值范围)

添加泛型约束收缩类型,主要有以下两种方式:

1、指定更具体的类型

function id<Type>(value:Type[]):Type[]{
    console.log(value.length)
    return value
}
//将类型修改为Type类型的数组

2、添加约束

interface ILength{length:number}
function id<Type extends ILength>(value:Type):Type{
    console.log(value.length)
    return value
}

解释:1、创建描述约束的接口ILength,该接口要求提供length属性

2、通过extends关键字使用该接口,为泛型(类型变量)添加约束

3、该约束表示:传入的类型必须具有length属性

注意:传入的实参(比如,数组)只要有length属性即可,这也符合前面讲到的接口的类型兼容性

泛型的类型变量可以有多个,并且类型变量之间还可以约束

function getProp<Type, key extends keyof Type>(obj:Type,key:Key){
    return obj[key]
}
let person = {name:'jack',age:18}
getProp(person,'name')

解释:1、添加了第二个类型变量key,两个类型变量之间使用(,)分隔

2、keyof关键字接收一个对象类型,生成其键名称(可能是字符串或数字)的联合类型

3、本示例中keyof Type实际上获取的是person对象所有键的联合类型,也就是:‘name’|‘age’

4、类型变量Key受Type约束,可以理解为:Key只能是Type所有键中的任意一个,或者说只能访问对象中存在的属性

泛型接口:

接口也可以配合泛型来使用,以增加其灵活性,增强其复用性

interface IdFunc<Type>{
    id:(value:Type)=>Type
    ids:()=>Type[]
}

let obj: IdFunc<number> = {
    id(value){return value},
    ids(){return [1,3,5]}
}

解释:1、在接口名称的后面添加<类型变量>,那么,这个接口就变成了泛型接口

2、接口的类型变量,对接口中所有其他成员可见,也就是接口中所有成员都可以使用类型变量

3、使用泛型接口时,需要显式指定具体的类型(比如,此处的idFunc)。

4.下hi,id方法的参数和返回值类型都是number;ids方法的返回值类型是number[]

泛型类:

class也可以配合泛型来使用

例如:react的class组件的基类Component就是泛型类,不同的组件有不同的props和state

interface IState{count:number}
interface Iprops{maxLength:number}
class InputCount extends React.Component<IProps,IState>{
    state:IState = {
        count:0
    }
    render(){
        return <div>{this.props.maxLength}</div>
    }
}
//解释:React.Component泛型类的两个类型变量,分别指定props和state类型

创建泛型类:

class GenericNumber<NumType>{
    defaultValue:NumType
    add:(x:NumType,y:NumType)=>NUmType
}

解释:1、类似于泛型接口,在class名称后面添加<类型变量>,这个类就变成了泛型类

2、此处的add方法,采用的是箭头函数形式

const myNum = new GenericNumber<number>()
myNum.defaultValue = 10

类似于泛型接口,在创建class实例时,在类名后面通过<类型>来指定明确的类型

泛型工具类型:

TS内置了一些常用的 工具类型,来简化TS中的一些常见操作

Partial

用来构造(创建)一个类型,将Type的所有属性设置为可选

interface Props{
    id:string
    children:number[]
}
type PartialProps = Partial<Props>

解释:构造出来的新类型PartialProps结构和Props相同,但所有的属性都变为可选的

Readonly

用来构造一个类型,将Type的所有属性都设置为只读

interface Props{
    id:string
    children:number[]
}
type ReadonlyProps = ReadOnly<Props>

解释:构造出来的新类型ReadOnlyProps结构和Props相同,但所有属性都变为只读的

let props:ReadonlyProps = {id:'1',children:[]}
props.id = '2'//无法赋值,因为属性只读

Pick<Type,Keys>

从Type中选择一组属性来构造新类型

interface Props{
    id:string
    title:string
    children:number[]
}
type PickProps = Pick<Props, 'id'|'title'>

解释:

  1. Pick工具类有两个类型变量:1表示选择谁的属性 2表示选择哪几个属性
  2. 其中第二个类型变量,如果只选择一个则只传入该属性名即可
  3. 第二个类型变量传入的属性只能是第一个类型变量中存在的属性
  4. 构造出来的新类型PickProps,只有id和title两个属性类型

Record<Keys,Type>

构造一个对象类型,属性键为Keys,属性类型为Type

type RecordObj = Record<'a'|'b'|'c',string[]>
let obj:RecordObj = {
    a:['1'],
    b:['2'],
    c:['3']
}

解释:

  1. Record工具类型有两个类型变量:1表示对象有哪些属性2表示对象属性的类型
  2. 构建的新对象类型RecordObj表示:这个对象有三个属性分别为a/b/c,属性值的类型都是string[ ]。

索引签名类型和索引查询类型

绝大多数情况下,我们都可以在使用对象前就确定对象的结构,并为对象添加准确的类型。

使用场景:当无法确定对象中有哪些属性(或者说对象中可以出现任意多个属性),此时,就用到索引签名类型了。

interface AnyObject{
    [key:string]:number
}
let obj: AnyObject = {
    a:1,
    b:2,
}

解释:

1.使用[key: string]来约束该接口中允许出现的属性名称。表示只要是string类型的属性名称,都可以出现在对象中。

2.这样,对象obj中就可以出现任意多个属性(比如,a、b等)。

3.**key只是一个占位符,**可以换成任意合法的变量名称。

4.隐藏的前置知识:JS中对象({})的键是string类型的。

映射类型

映射类型: 基于旧类型创建新类型(对象类型),减少重复、提升开发效率。

比如,类型PropKeys有x/y/z,另一个类型Type1中也有x/y/z,并且Type1中x/y/z的类型相同:

type PropKeys = 'x'|'y'|'z'
type Type1 = { x: number; y: number; z: number}

这样书写没错,但x/y/z重复书写了两次。像这种情况,就可以使用映射类型来进行简化。

type PropKeys = 'x'|'y'|'z'
type Type2 = {[Key in PropKeys]:number}

解释:

  1. 映射类型是基于索引签名类型的,所以,该语法类似于索引签名类型,也使用了。
  2. Key in PropKeys,表示Key可以是PropKeys联合类型中的任意一个,类似于forin(let k in obj)。
  3. 使用映射类型创建的新对象类型 Type2和类型 Type1结构完全相同。
  4. 注意:映射类型只能在类型别名中使用,不能在接口中使用。

映射类型除了根据联合类型创建新类型外,还可以根据对象类型来创建:

type Props = {a:number;b:string;c:boolean}
type Type3 = {[key in keyof Props]:number}

解释:

  1. 首先,先执行keyof Props 获取到对象类型Props中所有键的联合类型即,‘a’|‘b’|‘c’
  2. 然后,Key in …就表示Key可以是Props中所有的键名称中任意一个

命名空间

命名空间一个最明确的目的就是解决重名问题。

命名空间定义了标识符的可见范围,一个标识符可在多个名字空间中定义,它在不同名字空间中的含义是互不相干的。这样,在一个新的名字空间中可定义任何标识符,它们不会与任何已有的标识符发生冲突,因为已有的定义都处于其他名字空间中。

namespace:

namespace SomeNameSpaceName { 
   export interface ISomeInterfaceName {      }  
   export class SomeClassName {      }  
}

以上定义了一个命名空间 SomeNameSpaceName,如果我们需要在外部可以调用 SomeNameSpaceName 中的类和接口,则需要在类和接口添加 export 关键字。

在另一个命名空间调用语法格式为:

SomeNameSpaceName.SomeClassName;

如果一个命名空间在一个单独的 TypeScript 文件中,则应使用三斜杠 /// 引用它,语法格式如下:

/// <reference path = "SomeFileName.ts" />

嵌套命名空间

命名空间支持嵌套,即你可以将命名空间定义在另外一个命名空间里头。

namespace namespace_name1 { 
    export namespace namespace_name2 {
        export class class_name {    } 
    } 
}

成员的访问使用点号 . 来实现

模块

TypeScript 模块的设计理念是可以更换的组织代码。

模块是在其自身的作用域里执行,并不是在全局作用域,这意味着定义在模块里面的变量、函数和类等在模块外部是不可见的,除非明确

地使用 export 导出它们。类似地,我们必须通过 import 导入其他模块导出的变量、函数、类等。

两个模块之间的关系是通过在文件级别上使用 import 和 export 建立的。

模块导出使用关键字 export 关键字,语法格式如下:

// 文件名 : SomeInterface.ts 
export interface SomeInterface { 
   // 代码部分
}

要在另外一个文件使用该模块就需要使用 import 关键字来导入:

import someInterfaceRef = require("./SomeInterface");