它们的定义文件同样在 TypeScript 核心# TS笔记基础
1.原始数据类型
JavaScript的类型分为两种:原始数据类型(Primitive data types)和对象类型(Object types)。
原始数据类型包括:字符串
,数字
,布尔值
,null
,undefind
已经es6新增的symbol
和es10新增的bigInt
。
布尔值
let isDone:boolean = false;
// 编译通过
// 后面约定,未强调编译错误的代码片段,一律未编译通过
注意,使用构造函数创建的boolean
不是布尔值
let isDone:boolean = nwe Boolean(1);
// Type 'Boolean' is not assignable to type 'boolean'.
// boolean' is a primitive, but 'Boolean' is a wrapper object.
// Prefer using 'boolean' when possible.
使用Boolean创建的不是一个布尔值,它实际上是一个boolean
对象。
在TypeScript里,boolean
表示基本数据类型,而Boolean
则是JavaScript的一个构造函数。其他基本类型一样(除了undefined
和null
)。
数值
使用number
定义类型
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;
字符串
使用string
定义类型
let myName:string = '小明';
leg age:string = '18';
let my:string = `我的名字${
age}`;
空值
在JavaScript
中没有空值(void
)的概念, 在 TypeScript
中 ,一般空值用于函数的返回.
function alertName():void{
alart('我是图图小可爱')
return;
// return 不能返回结果,
}
void
函数返回或者不返回,实际上在空值也是一种返回,返回空
Undefind和Null
使用undefind
和null
定义类型
let u:undefind = undefind;
let n:null = null;
与void
的区别是undefind
和null
是所有类型的子类型。也就是说undefind
和null
可以赋值所有的已定义类型变量
let s:string = undefind;
let s1:string = null;
let num:number = undefind;
let num1:number = null;
而 void
类型的变量不能赋值给 number
类型的变量
let u:void;
let num:number = u;
// Type 'void' is not assignable to type 'number'.
2.任意值
任意值(any)表示允许赋值成任意的值。一般不会用。
什么是任意值
let s1:string = 'seven';
s1 = 10;
// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'
不允许从字符串改变成数字,但如果是any
类型,则 允许被赋值为任意类型
let s1:any = 'seven';
s1 = 10
任意值的属性和方法
在任意值上访问任意属性都是允许的
let anyTing:any = 'hello';
console.log(anyTing.myName);
console.log(anyTing.firstName)
在任意值上访问任意方法都是允许的
let anyTing:any = 'Tom';
anyTing.setName('Jeary');
anyTing.setName('Jeary').setHello();
可以认为,声明一个任意值之后,对它的如何操作,返回的内容的类型都是任意值 。
未声明类型
let someTing;
someTing = 10;
someTing = '你好';
等价于
let someTing:any;
someTing = 10;
someTing = '你好';
变量声明未赋值类型,默认是任意值的类型。
3.类型推论
如果没有明确指定类型,那么TypeScript会根据类型推论
来的规则来推断出一个类型
什么是类型推论
let myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.
实际上,它会等价于:
let myFavoriteNumber:string = 'seven';
myFavoriteNumber = 7;
// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.
TypeScript 会在没有明确的指定类型的时候推测出一个类型,这就是类型推论。
如果声明没定义类型,不管之后有没有赋值,它都会被推断为any
类型
let someTing;
somgTing = 'seven';
someTing = 7
4.联合类型
联合类型表示可以取多种类型中的一中
什么是联合类型
let s1:number | string = 10;
s1 = 'seven';
s1 = 1;
联合类型使用 |
分隔每个类型。
这里的s1表示允许类型为number
和string
,而不能为其他类型
访问联合类型的属性或方法
function fn(s1:string|number):number{
return s1.length
}
// index.ts(2,22): error TS2339: Property 'length' does not exist on type 'string|number'.
// Property 'length' does not exist on type 'number'.
访问的不是string
和number
的共同方法
访问 string
和 number
的共有属性是没问题的:
function fn(s1:string|number):number{
return s1.toString()
}
联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型:
let s1:string | number;
s1 = '1111';
console.log(s1.length);
s1 = 7;
console.log(s1.length) // 报错
5.对象的类型-接口
使用对象定义类型
let name:{
};
let name1:object;
一般实际中,我们不会这么定义类型,这样定义了,对象中的属性相当于any
类型
let nName:{
name:string,age:number};
指定对象中定义的类型,name
和age
属性名必须保存一致,不能多也不能少
let nName:{
name:string,age?:number};
当前对象name传递的?.
表示可传可不传;
let nName:{
name:string,age?:number,[propName:string]:any}
[propName:string]表示传入一个未知的属性,必须是字符串类型的,[propName:string]:any的any
表示子类型可以是任意值
let nName:{
name:string,age?:number,[propName:string]:string}
指定子类型为string
,如果传入了age属性就会报错,类型为string才行。
通常可以通过联合类型来解决这种问题
let nName:{
name:string,age?:number,[propName:string]:string|number}
什么是接口
在TypeScript中,我们使用接口( Interfaces )来定义对象的类型。
在面对对象语言中,有个很重要的概念 ,它是对行为的抽象,而具体到如何行动需要由类(class)去实现( implement )
TypeScript的接口是一个非常灵活的概念,除了对类的一部分进行抽象以为,还可以对类的形状进行描述。
简单的例子
interface Person{
name:string,
age:number
}
let zhangshan:Person{
name:'张三',
age:16
}
定义了名叫Person
的接口,在zhangsan
中对象的形状必须要和Person
一样,一般接口名称大写,用于区分。
interface Person {
name: string;
age: number;
}
let tom: Person = {
name: 'Tom'
};
// index.ts(6,5): error TS2322: Type '{ name: string; }' is not assignable to type 'Person'.
// Property 'age' is missing in type '{ name: string; }'.
定义的变量比接口少了一些属性是不允许的
interface Person {
name: string;
age: number;
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};
// index.ts(9,5): error TS2322: 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 tom:Person={
name:"丈夫"
}
// age可传可不传
这时, **仍不许添加新的未定义的属性 **
任意属性
有时候我们希望一个接口允许有任意的属性,可以使用如下方式
interface Person{
name:string,
age?:number,
[proprsName:string]:any
}
let tom:Person={
name:"丈夫",
sss:1231231
}
使用 [propName: string]
定义了任意属性取 string
类型的值。
需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集
interface Person{
name:string,
age?:number,
[proprsName:string]:string
}
let tom:Person={
name:"丈夫",
sss:"1231231"
}
指定任意类型为字符串,此时仍不能新增未定义的属性
interface Person{
name:string,
age?:number,
[proprsName:string]:string
}
let tom:Person={
name:"丈夫",
age:10
sss:"1231231"
}
// 报错,指定了sss子类型为string类型,age的类型为number
任意属性的值允许是 string
,但是可选属性 age
的值却是 number
,number
不是 string
的子属性,所以报错了 注意一个接口只能指定一个任意类型
一个接口中只能定义一个任意属性。如果接口中有多个类型的属性,则可以在任意属性中使用联合类型:
interface Person{
name:string,
age?:number,
[proprsName:string]:string|number
}
let tom:Person={
name:"丈夫",
age:10
gender:"男"
}
只读属性
interface Person{
readonly id:number,
name:string,
age?:number,
[proprsName:string]:string|number
}
let tom: Person = {
id: 89757,
name: 'Tom',
gender: 'male'
};
tom.id = 9527;
// index.ts(14,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.
上例中,使用 readonly
定义的属性 id
初始化后,又被赋值了,所以报错了
注意,只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候:
interface Person {
readonly id: number;
name: string;
age?: number;
[propName: string]: any;
}
let tom: Person = {
name: 'Tom',
gender: 'male'
};
tom.id = 89757;
// 1. id未传 报错 2.id为只读,进行了第二次赋值 报错
6.数组的类型
在TypeScript中,定义数组的类型有多种方式,比较灵活
【类型+方括号】表示法
let fibonacci:number[] = [1,2,3,4,5];
数组中不允许出现其他的类型
let fibonacci:number[]= [1,'we',2,3]
// Type 'string' is not assignable to type 'number'.
数组的一些方法的参数也会根据数组在定义时约定的类型进行限制
let fibonacci:number[]= [1,'we',2,3]
fibonacci.push('8')
// Argument of type '"8"' is not assignable to parameter of type 'number'.
要求传入number
类型的,却传入string
类型,报错
数组泛型
我们也可以使用数组泛型(Array Generic) Array
来表示数组
let fibonacci :<Array>number = [1,2,3,4,5];
用接口来表示数组类型
interface ArrayNumber{
[propsName:number]:number
}
虽然接口也可以用来描述数组,但是我们一般不会这么做,因为这种方式比前两种方式复杂多了。
不过有一种情况例外,那就是它常用来表示类数组。
类数组
类数组(Array -like Object)不是一个数组,比如arguments
function fn(){
let args:number[] = arguments;
}
// Type 'IArguments' is missing the following properties from type 'number[]': pop, push, concat, join, and 24 more.
上例中,arguments
实际上是一个类数组,不能用普通的数组的方式来描述,描述的不全面,数组里不可能都是数字,而应该用接口的方式来定义
function fn(){
let args:{
[index:number]:number,
length:number,
callee:Function,
} = arguments;
}
在这个例子中,我们除了约束当索引的类型是数字时,值的类型必须是数字之外,也约束了它还有 length
和 callee
两个属性。
事实上常用的类数组都有自己的接口定义,如 IArguments
, NodeList
, HTMLCollection
等
functions sum(){
let args:IArguments = arguments;
}
其中IArguments
是TypeScript中定义好了的类型,它实际上就是:
interface IArguments{
[index:number]:number,
length:number;
callee:Function;
}
any在数组中的使用
一个比较常见的做法是,用 any
表示数组中允许出现任意类型:
let list:<Array>any = ['xcatilu',25,{
website:"http://localhost:3000"}]
7.函数的类型
函数是JavaScript的第一等公民
函数声明(匿名函数)
在JavaScript中,有两种常见的定义函数的方式–匿名函数(Function Declaration)和 函数表达式 ( Function Expression )
// 匿名函数
function sum(x,y){
return x+y;
}
// 函数表示式
const sun = function(x,y){
return x+y;
}
一个函数有输入和输出,要在 TypeScript 中对其进行约束,需要把输入和输出都考虑到,其中函数声明的类型定义较简单:
function (x:number,y:number):number{
return x+y
}
注意,输入多余的(或者少于要求的)参数,是不被允许的:
function sum(x: number, y: number): number {
return x + y;
}
sum(1, 2, 3);
// index.ts(4,1): error TS2346: Supplied parameters do not match any signature of call target.
function sum(x: number, y: number): number {
return x + y;
}
sum(1);
// index.ts(4,1): error TS2346: Supplied parameters do not match any signature of call target.
函数表达式
如果要我们现在写一个对函数表达式(Function Expression)的定义,可能会写成这样:
let sum = function(a:number,b:number):number{
return a+b
}
不过这样的实际上是对function进行的类型定义,而左边的sum则是推论出来的,正确的写法为
let sun:(x:number,y:number)=>number=function(x:number,y:number):number{
return x+y;
}
注意不要混淆了 TypeScript 中的 =>
和 ES6 中的 =>
。
在 TypeScript 的类型定义中,=>
用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。
用接口定义函数的形状
我们也可以使用接口的方式来定义一个函数需要符合的形状
interface SearchFunc {
(source:string,subString:string):boolean;
}
let mySearch:SearchFunc;
mySearch=function(source:string,subString:string){
return source.search(subString)!==-1;
}
采用函数表达式|接口定义函数的方式时,对等号左侧进行类型限制,可以保证以后对函数名赋值时保证参数个数、参数类型、返回值类型不变。
可选参数
前面提到,输入多余的或者少于要求的参数是不允许的。那么可选参数如何定义
functions fn(s1:string,s2?:string):string{
return s1+' '+s2
}
需要注意的是,可选参数必须在必需参数后面,换句话说,可选参数后面不允许再出现必需参数了
function buildName(firstName?: string, lastName: string) {
if (firstName) {
return firstName + ' ' + lastName;
} else {
return lastName;
}
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName(undefined, 'Tom');
// index.ts(1,40): error TS1016: A required parameter cannot follow an optional parameter.
默认参数
在ES6中,我们允许给函数的参数添加默认参数,TypeScript会将添加的默认参数识别为可选参数。
functions fn(firstName:string,lastName:string='tom'){
return firstName+' '+ lastName
}
fn('xiaoming','tools')
fn('yue')
此时就不受「可选参数必须接在必需参数后面」的限制了
functions fn(firstName:string='tom',lastName:string){
return firstName+' '+ lastName
}
fn('xiaoming','tools')
fn('yue')
剩余参数
ES6中,可以使用…rest的方式获取函数中的剩余参数(rest参数)
function push(s1,...rest){
rest.forEach(v=>s1.push(v));
}
let a:<Array>any = [];
push(a,1,2,3)
事实上,rest是一个数组。所以我们可以用数组的类型来定义它
function push(array:any[],...rest:any[]){
rest.forEach(item=>array.push(item))
}
let a = [];
push(a, 1, 2, 3);
注意,rest参数只能是最后一个参数。
重载
重载允许一个函数接受不同数量或者类型的参数,做出不同的处理
functions reverse(x:string|number):string|number|void{
if(typeof x === 'string'){
return x.split('').reverse().join('')
}else if(typeof x === 'number'){
return Number(x.toString().split('').reverse().join(''))
}
}
然而这样有一个缺点,就是不能够精确表达,输入为数字的时候,输出也应该是为数字,输入字符串的时候,输出也应该为字符串
这时,我们可以使用重载定义多个 reverse
的函数类型:
function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string | void {
if (typeof x === 'number') {
return Number(x.toString().split('').reverse().join(''));
} else if (typeof x === 'string') {
return x.split('').reverse().join('');
}
}
上例中,我们重复定义了多次函数 reverse
,前几次都是函数定义,最后一次是函数实现。在编辑器的代码提示中,可以正确的看到前两个提示。
注意,TypeScript 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。
8.类型断言
类型断言(Type Assertion)可以用来手动指定一个值的类型。
值 as 类型
或者
<类型>值
在 tsx 语法(React 的 jsx 语法的 ts 版)中必须使用前者,即 值 as 类型
。
形如 的语法在 tsx 中表示的是一个 ReactNode
,在 ts 中除了表示类型断言之外,也可能是表示一个泛型
故建议大家在使用类型断言时,统一使用 值 as 类型
这样的语法。
let a:unknown;
a='xxxxx';
let b:string;
b= a as string;
interface Cat{
name:string,
run():void;
}
interface Fish{
name:string,
swim():viod;
}
function swim(animal:Cat|Fish){
if(typeof (anumal as Fish).swim === 'function'){
return true
}
return false
}
const cat:Cat={
name:"张三",
run(){
console.log('run')
}
}
swim(cat);
9.内置对象
JavaScript中有很多内置对象,它们可以之间在TypeScript中当作定义好了的类型
内置对象是根据标准在全局作用域上存在的对象,这里的标准指的是ECMAScript和其他环境(Dom)的标准
ECMAScript 的内置对象
ECMAScript 标准提供的内置对象有
Boolean
、Error
、Date
、RegExp
等。
let b: Boolean = new Boolean(1);
let e: Error = new Error('Error occurred');
let d: Date = new Date();
let r: RegExp = /[a-z]/;
DOM 和 BOM 的内置对象
Document
、HTMLElement
、Event
、NodeList
等。
let body: HTMLElement = document.body;
let allDiv: NodeList = document.querySelectorAll('div');
document.addEventListener('click', function(e: MouseEvent) {
// Do something
});
它们的定义文件同样在 TypeScript 核心库的定义文件中。
库的定义文件中。