前言
从最常用到的js基础入手,无论做什么都离不开数据基础类型和函数,今天就从这两大类开始入手。
前提小基础
?
?代表当前属性或者参数为可选项
readonly
readonly 代表设置当前属性或参数为只读类型
基础类型
js的基础类型
string、number、boolean、null、undefined、symbol、bigint
TypeScript基础类型声明
const myName: string = 'linbudu';
const age: number = 24;
const male: boolean = false;
const undef: undefined = undefined;
const nul: null = null;
const obj: object = { myName, age, male };
const bigint_my: bigint = BigInt(9007199254740991);
const symbol_my: symbol = Symbol('unique');
null和undefined
null 和 undefined在js里都可以表示为空,undefined是已声明未定义,null是已声明,但未有值。
- 相同点
-
值相等
null==undefined // ture
-
转换为boolean时都为false
console.log( Boolean(undefined) )//false console.log( Boolean(null) )//false
-
- 不同点
- 类型不相等
null===undefined // false
- 转换为number时值不同
console.log( Number(undefined) )//NaN console.log( Number(null) )//0
- 类型不相等
在typeScipt当中,null和undefined都是具有实际意义的类型,在没有开启strictNullCheck(tsconfig文件配置)
的情况下会被认为包含在其他类型里。
const null_type: null = null;
const undefined_type: undefined = undefined;
const null_string: string = null; // 仅在关闭 strictNullChecks 时成立,下同
const undefined_number: number = undefined;
引用类型
数组
校验元组数据类型
js声明数组未定义数组元祖具体类型,以及数组长度,typeScript里可以指定元组的数据类型,以及校验数组边界。
let list1: number[] = [1, 2, 3];
let list2: Array<string>=[]
// 以上简洁的规定了数组元组的数据类型必须是string,也可以具体规定某一位置的元组的数据类型。
const list3: [string, string, string] = ['li', 'shu', 'tong'];
const list4: [number, boolean, string] = [1, false, 'tong'];
// 如果试图改写
list3[1]=1 // ts报错指出不能写入number类型
list4[0]='1' // ts报错指出不能写入string类型
数组长度约束
const list5: [string, number, boolean] = ['linbudu', 599, true];
list5[3] = 1 // ts报错指出数组长度为3,在索引3处没有元素。
如果数组的元祖又不一定每个都有怎么办?
const list6: [number, boolean?, string?] = [1, , ];
boolean?
可以理解为boolean|undefined
,该位置元素可以为空,因为元素约束变了,数组的边界也随之改变,list6的长度可以是1|2|3
具名元组
多人合作的时候,不一定知道别人声明的数组里具体代表的是什么业务含义,只能看到传入的数据类型也会一头雾水啊,有没有更明确的方法知道它究竟代表什么呢?有!TS4里提出了具名元组的概念。
// 以下对元组的含义具名化
const arr7: [count: string, isPermission:boolean, countName: string] = [599, false, 'total'];
// or
const arr7: [count: string, isPermission?:boolean, countName?: string] = [599, false, 'total'];
小细节
es6里有解构赋值,解构赋值的时候无法知道具体有多少个元素,属于隐式访问数组边界及类型,可能会跑出警告。 对于数组,此时仍然无法检查出是否存在隐式访问,因为类型层面并不知道它到底有多少个元素。但对于元组,隐式的越界访问也能够被揪出来给一个警告:
const arr1: string[] = [];
const [ele1, ele2, ...rest] = arr1;
对象
接口inteface
js里对对象的使用是十分频繁的,新手程序员经常会因为不注意引用的使用导致各种小问题。TypeScript的核心原则之一是对值所具有的结构进行类型检查,那在TS里是怎么描述对象的呢?
function printLabel(labelledObj: { label: string }) {
console.log(labelledObj.label);
}
let myObj = { size: 10, label: "Size 10 Object" };
printLabel(myObj);
可以改写为
interface LabelledValue {
label: string;
}
function printLabel(labelledObj: LabelledValue) {
console.log(labelledObj.label);
}
let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);
以上规定了:
- 名为lable的参数数据类型为string
- 必须包含名为lable的参数
类型检查器不会检查属性的顺序,只要符合条件那他就是被允许的。
修饰接口属性
可选属性
和上面数组里的可选属性一样,?代表的是指定数组类型|undefined
interface LabelledValue {
tag: string,
label?: string;
}
// 以下是被允许的
const obj: LabelledValue = {
tag: '必须包含的属性'
// label为可选项
}
只读属性
只读,有些属性是定义的时候就已经约定好了,并不希望后期迭代过程中修改他,那可以用readonly来指定他们。
interface LabelledValue {
tag: string,
label?: string,
readonly read: string
}
// 以下是被允许的
const obj: LabelledValue = {
tag: '必须包含的属性',
// label为可选项
read: '只读属性'
}
obj.read = '试图修改read属性' // ts报错提示read只读
同样数组也可以设置只读ReadonlyArray<T>
, 而基础类型的只读可以用const代替。
额外的属性检查
一切都是那么美好,这样一来再也不会有人在迭代的过程中在迭代的过程中不经意间修改对象里的属性导致出错了,然而还是太天真的。如果我们想额外增加参数就显得格外拮据!不论是你写错变量名还是新增变量名,ts都会告诉你该属性不在指定类型中,这种情况如果是把整个Obj指定为any,似乎又不符合我需要关注部分变量的需求。别慌,我们可以添加一个字符串索引签名。
interface LabelledValue {
tag: string,
label?: string,
readonly read: string,
[propName: string]: any;
}
索引值类型
// 数组类型
interface StringArray {
[index: number]: string;
}
let myArray: StringArray;
myArray = ["Bob", "Fred"];
// 对象引用类型
class Animal {
name: string;
}
class Dog extends Animal {
breed: string;
}
// 错误:使用数值型的字符串索引,有时会得到完全不同的Animal!
interface NotOkay {
[x: number]: Animal;
// [y: string]: Dog;
}
函数
函数也可以利用interface接口描述。
函数的类型签名
interface SearchFunc {
(source: string, subString: string): boolean;
// 入参数:出参
}
let fun: searchFunc
let mySearch: SearchFunc;
mySearch = (source: string, subString: string)=>{
let result = source.search(subString);
return result > -1;
};
// 也可以简写为
const mySearch:(source: string, subString: string)=>boolean = (source, subString)=>{
let result = source.search(subString);
return result > -1;
}
// 也可以简写为
const mySearch:(source: string, subString: string):boolean = (source, subString)=>{
let result = source.search(subString);
return result > -1;
}
// 这样是不是就简单多了,但是如果参数比较多的时候,写起来仍旧比较麻烦,也不利于阅读,那我们可以把他们拆分一下
type searchFunc=(source: string, subString: string)=>boolean
const mySearch:searchFunc=(source, subString)=>{
let result = source.search(subString);
return result > -1;
}
可选参数
根据前提小知识可以轻而易举的理解,如果我们要设置一个参数为可选参数,那我们可以这样写:
function foo(fist: string, second?: number){
// second为可选参数
}
值得注意的是,可选参数必须跟在必选参数后面,如果可选参数是first,second为必选参数,那么first和second的参数位置则需要调换一下
默认参数
直接为可选参数设置默认值,如果没有该参数则函数会读取默认参数
function foo(fist: string, second: number=18){
// const currentAge = second||18
// second为可选参数
}
rest
就想前面讲到的对象一样,有时候我们并不需要关注所有参数,那剩下不需要那么关注的参数应该怎么设置呢?我想到js函数参数本身有一个arguments
或者rest
,哦对了,参数可以是一个类数组或者数组,那ts用rest来表示剩余参数,rest是一个数组,那他可以用之前讲到过的数组描述方法来描述它,比如:
function foo(arg1: string, ...rest: any[]) { }
当然你也可以描述为:
function foo(arg1: string, ...rest: [string, number]) { }
void类型
void相当于立即执行一个函数,void 操作符强制将后面的函数声明转化为了表达式,因此整体其实相当于:void((function foo(){})())
void (function foo() {
console.log("Look at me!");
})();
一个没有显式返回结果的函数,将会被推导为void
function foo1(){}
function foo2(){
return;
}
// 显式返回null
funciton foo3(){
return null
}
undefined和null都是具有具体类型含义的,但是void也可以理解为一个空类型,如果在非严格模式下(strictNullCheck tsconfig配置),void可被赋值为undefined或者null
const voidVar1: void = undefined;
const voidVar2: void = null; // 需要关闭 strictNullChecks
函数重载
前面讲了函数的入参和出参怎么描述,但是实际上,函数足够复杂的时候,入参和出参可能不是固定的:
function func(foo: number, bar?: number): string | number|undefined {
switch (bar) {
case 1:
return String(foo);
case 2:
return foo * 599;
default:
return undefined
}
}
从这个函数描述里,我们可以知道入参和出参的情况,但是并没有把入参和出参关联起来,我们只能知道它是个联合类型,关系不够明朗。
TS也为我们考虑到了这一点,利用函数重载可以将入参与出参之间的关系。
以上函数可以改写为下:
function func(foo: number, bar: 1): string;
function func(foo: number, bar: 2): number;
function func(foo: number, bar: undefined): undefined;
function func(foo: number, bar?: number): string | number|undefined {
switch (bar) {
case 1:
return String(foo);
case 2:
return foo * 599;
default:
return undefined
}
}
// 异步函数、Generator 函数等类型签名
function func(foo: number, bar: 1): string
输入参数bar为1时,出参为string类型。function func(foo: number, bar: 2): number
输入参数为2时,出参为number类型。function func(foo: number, bar: undefined): undefined
输入参数为undefined时,也就是不输入参数时,出参为undefined。
基于重载签名,我们就实现了将入参类型和返回值类型的可能情况进行关联,获得了更精确的类型标注能力。