bunny笔记|TS基础(1):基础类型定义、回调签名、构造签名、泛型、重载函数等

  • 全局安装typescript
  • 新建文件夹,文件夹内再建一个src文件夹
    tsc --init (生成tsconfig.json文件,修改 “rootDir”: “./src”,和"outDir": “./dist”)
    tsc --watch (监听)
  • 查看console.log()要进入到dist文件里执行node xx.js来运行

基础类型的定义

字符串、数字、布尔类型、数组类型…

let str:string='hello typescript';
let num:number=333;
let bool:boolean=false;
let arr:number[]=[1,2,3,4]
console.log(str);
函数类型表达式
//fn:(a:string)=>void
// function greeter(fn:(a:string)=>void){
//     fn('hello')
// }

// function printToConsole(s:string){
//     console.log(s)
// }

// greeter(printToConsole)

//也可以用一个type来定义函数参数
type GreetFunction = (a: string) => void
function greeter(fn: GreetFunction) {
    fn('hello')
}

function printToConsole(s: string) {
    console.log(s)
}

greeter(printToConsole)

回调签名

//给函数添加属性,要通过对象的方式来定义
type DescribableFunction ={
    //在对象里定义一个属性名字discription,并给它定义一个string类型
    description:string
    //再定义一个函数体 (函数参数:类型):返回值的类型
    //注意:在参数列表和返回类型之间使用:而不是=>
    (someArg:number):boolean
} 

//定义一个doSomething函数调用函数参数,业务代码处理部分
function doSomething(fn:DescribableFunction){
    //调用函数属性和函数本身(函数要传递一个number类型,这里传入一个6)
    console.log(fn.description + 'return' + fn(6));
}

//doSomething需要传入一个参数fn,这里定义一个fn1将参数传给doSomething()
function fn1(n:number){
    console.log(n);
    //这里要返回一个boolean类型
    return true
}

//那么fn1就可以绑定属性传参了,注意这里绑定的属性必须是type DescribableFunction{}对象里定义过的属性
fn1.description='hello'
//doSomething就可以将一个function传入了,fn1满足了返回的类型
doSomething(fn1)

构造签名

//2.构造一个类
class Ctor {
    //类里顶一个参数s类型sting
    s: string
    //再定义一个构造函数,这个函数需要传入一个s参数string类型,然后把参数s赋值给this.s
    constructor(s: string) {
        this.s = s
    }
}
//1.通过type来定义一个构造签名,取名为SomeConstructor,后面是对象
type SomeConstructor = {

    //在对象里先定义一个调用签名 格式:(函数的参数:参数的类型):返回的类型/构造的类
    //构造类的话,要先在前面定义该类
    new (s: string): Ctor
}
//3.使用函数 参数为Ctor:类型为type定义好的类型名:SomeConstructor
function Fn(Ctor: SomeConstructor) {
    ////Ctor: SomeConstructor可以理解为一个构造函数,那么这里就可以调用这个函数了,Ctor('字符串'),但是这里只实现了调用签名,并没有实现构造签名,在ts里要实现构造签名,需要在type SomeConstructor={}对象里的方法添加一个new,这样它就是一个构造函数类型了,那么在这里的Ctor('字符串')前面也要加一个new才能够被实例化。如果要返回这个实例,前面再加一个return,那么这里就返回了一个构造函数的实例了
   return new Ctor('字符串');
}

//4.调用函数,传入的参数是构造函数类型,这里传入Ctor
const fn = Fn(Ctor)
//5.打印,运行 node 05-constructor.js
console.log(fn.s);

其它:
有的对象,比如JavaScript的Date对象,可以在有 new或者没有new的情况下,都可以被调用,那我们可以在同一个类型中,任意地结合调用和构造签名
例如:

//1.定义一个接口,名字CallOrConstructor,可以调用也可以构造的意思
interface CallOrConstructor {

    //(2)再构造一个函数,返回一个Date类型。这叫构造签名的方式
    new(s: string): Date
    //(1)定义一个函数体,传参为可选型。这叫调用签名的方式
    (n?: number): number
}

//2.使用接口类型
function Fn(date: CallOrConstructor) {
    //使用构造签名函数,new实例化date再传入字符串参数
    let d = new date('2000-10-2')
    //使用调用签名
    let n = date(100)
}

//以上,实现了结合使用调用签名和构造签名 两种方法 去定义了一个类型

泛型

// function firstElement1(arr:any[]){
//     return 100
// }

// firstElement1([2,'v'])


// function firstElement(arr:Type[]):Type|undefined{
//     return arr[0]
// }

// //指定类型
// firstElement(['a','b','c'])//string类型
// firstElement([1,2,3])//number类型
// firstElement([])//undefined类型

// //也可以不写定义类型
// firstElement(['a','b','c'])//string类型
// firstElement([1,2,3])//number类型
// firstElement([])//undefined类型

//泛型可以定义1个 也可以定义多个,泛型类型的名字是可以任意取的,但是要保证使用泛型时与命名时的名称一致
//定义一个map函数,两个参数arr和func 
//定义泛型类型,Input和Output,arr为Input数组类型,func为一个输入函数arg为Input的函数且返回Output类型的方法,map函数最终的返回值是Output数组类型
function map(arr: Input[], func: (arg: Input) => Output): Output[] {
    return arr.map(func)
}

//声明一个变量parsed来调用map函数
const parsed = map(['1', '2', '3'], (n) => parseInt(n))

//map函数的第一个参数是数字字符串类型,所以Input为string类型
//map函数的第二个参数是将函数arg传入参数类型为Input的数字字符串通过parseInt()方法转化为真正的数字,所以Input是string类型,Output是number类型。

泛型限制条件

限制条件 关键字:extends

//1.定义一个longest方法,类型为Type,两个参数,参数的类型也为Type
function longest (a: Type, b:Type) {

    //2.if做条件限制
    //3.length找不到,则在方法指定的类型Type后面添加extends继承length指定类型的对象
    if (a.length >= b.length) {
        return a
    } else {
        return b
    }

}

const longestArray =longest([1,2],[1,3,5,7])//数组length属性
const longestString = longest('hello','world')//字符串有length属性
// const notOk=longest(122,333)//报错  因为数字没有length属性

指定类型参数

function combine(arr1: Type[], arr2: Type[]): Type[] {
    return arr1.concat(arr2)
}

const arr3 = combine(["string"], [1, 2, 3])
console.log(arr3);

可选参数

//?
function a(a:number,b?:number){
    
}

重载函数

function makeDate(timestamp: number): Date
function makeDate(m: number, d: number, y: number): Date
function makeDate(mOrTimestamp: number, d?: number, y?: number): Date {
    if (d !== undefined && y !== undefined) {
return new Date(y,mOrTimestamp,d)
    }else{
        return new Date(mOrTimestamp)
    }
}

const d1=makeDate(12345678)
const d2=makeDate(2,3,4)
//const d3=makeDate(3,4)//报错

//注意:这里没有需要2个参数的重载,但存在需要1或3参数的重载
this

//1.定义一个接口 User,定义一个属性admin是一个布尔值
interface User {
    admin: boolean
}

//2.定义一个接口DB 里面是一个函数,然后定义函数的参数和参数的类型以及返回值的类型User[]
//函数名filter,函数参数名this,函数类型User,函数返回值是boolean
interface DB {
    filterUsers(filter: (this: User) => boolean): User[]
}

//3.实例化DB
//定义一个常量db,类型为DB的对象
//里面必须有一个filterUsers:()=>{}函数
//在()里实现一个filter函数
//然后再定义函数体,定义两个对象user1和user2
const db: DB = {
    filterUsers: (filter: (this: User) => boolean) => {
        let user1: User = {
            admin: true
        }
        let user2: User = {
            admin: false
        }
        return [user1, user2]
    }
}

//4.调用filterUsers函数。先定义一个常量admins接收函数调用返回的值
const admins = db.filterUsers(function (this: User) {
    return this.admin
})

console.log(admins);

参数展开运算符

//参数展开运算符(形参)
function multiply(n: number, ...m: number[]){

    //实现n乘以数组里的值组成新的数组
    return m.map((x) => n * x);
}
//n是10,...m是22, 3, 4, 6, 7
const b = multiply(10, 22, 3, 4, 6, 7);

//...m的类型一定是数组类型,number[]/any[]...

//参数展开运算符(实参)
const arr1=[1,2,4]
const arr2=[5,6,7]
arr1.push(...arr2)
console.log(arr1);

//得到arr1:[1,2,4,5,6,7]


//展开运算符运用Math方法,可以在数组后添加 as const 声明为常量,就可以正常使用了
const arg =[8,5] as const
const angle=Math.atan2(...arg)
console.log(angle);

对象类型最基本的定义方法

//方法1:通过匿名的方法来去定义一个对象的类型
function greet1(person: { name: string, age: number }) {
    return 'hello' + person.name
}

//方法2:通过接口interface的方式来定义一个Person类型
interface Person1 {
    name: string,
    age: number
}

function greet2(person: Person1) {
    return 'hello' + person.name
}


//方法3:也可以通过一个类型别名的方法来定义对象类型
type Person2 = {
    name: string,
    age: number
}

function greet3(person: Person2) {
    return 'hello' + person.name
}

对象可选属性

//属性修改器

//3.type声明该属性类型的对象,这里先给个空对象
type Shape = {}

//2.通过type或者interface接口的方式定义参数的类型PaintShape对象,添加shape属性对象类型
interface PaintShape {
    shape: Shape,
    //4.在PaintShape接口总还需要传入x和y的位置坐标,均为number类型,且为可选参数类型
    xPos?: number,
    yPos?: number
}

//1.定义一个函数,paintShape,传入一个参数opts,参数类型为PaintShape
// function paintShape(opts:PaintShape){

// }


//6.使用方法
// function paintShape(opts:PaintShape){
//     let xPos=opts.xPos
//     let yPos=opts.yPos
//     console.log(xPos);
//     console.log(yPos);
// }
//console.log为undefined,因为用户没有传入xPos和yPos的初始值

//7.将undefined去掉
// function paintShape(opts:PaintShape){
//     let xPos=opts.xPos===undefined?0:opts.xPos
//     let yPos=opts.yPos===undefined?0:opts.yPos
//     console.log(xPos);
//     console.log(yPos);
// } 
//console.log为0

//8.通过解构的方式,赋初始值
// function paintShape({ shape, xPos = 0, yPos = 0 }: PaintShape) {
//     console.log(shape);
//     console.log(xPos );
// }
//console.log为0
//如果要在解构的时候加入类型,如下展示
//此时的Shape和number并不是类型,而是别名,console也只能console它们定义的别名
//所以,切记不要在解构的时候定义类型
function paintShape({ shape: Shape, xPos:number = 0, yPos = 0 }: PaintShape) {
    console.log(Shape);
    console.log(number );
    console.log(yPos);
    
}

//5.调用
//这里的const shape:Shape={}空对象和type Shape={}空对象不是一个含义,const shape:Shape={}是具体的值,type Shape={}是类型的定义
const shape: Shape = {}
//调用且传入参数
paintShape({ shape })
// paintShape({shape,xPos:100})
// paintShape({shape,xPos:100,yPos:90})

只读属性

interface DoSomething {
    readonly prop: string
}

function doSomething(obj: DoSomething) {
    console.log(obj.prop);
    //修改prop --报错
    //obj.prop='hello'
}

interface Home{
    readonly resident:{
        name:string,
        age:number
    }
}

function visit(home:Home){
    console.log(home.resident.name);
    home.resident.age++//可正常应用,修改属性值
}

function evict(home:Home){
    // home.resident={
    //     name:'bunny',
    //     age:18
    // }

    //这样应用是报错的,因为resident为只读属性,也就是说resident定义为只读属性,那么resident本身是不能访问的,但是resident它里面的属性是可以访问的。
    //也就是说我们是可以分层地去定义类型属性readonly的
}

interface Person{
    name:string,
    age:number
}

interface ReadonlyPerson{
    readonly name:string,
    readonly age:number
}

let WritablePerson:Person={
    name:'bunny',
    age:16
}

//let readonlyPerson:ReadonlyPerson={} //报错
//但是:请注意,如下,是可行,也就是说,可修改的属性可以赋值给只读属性的对象
let readonlyPerson:ReadonlyPerson= WritablePerson

console.log(readonlyPerson.age);
WritablePerson.age++
console.log(readonlyPerson.age);

索引签名

interface StringArray{
    [index:number]:string
}

const myArray:StringArray=['b','b7']
const secondItem=myArray[0]

interface Animal{
    name:string
}

interface Doc extends Animal{
    breed:string
}

interface NotOkay{
    [index:string]:number|string
    length:number
    name:string
}
const notOkay:NotOkay={
    x:100,
    length:90,
    name:'bunny'
}

//定义索引签名 也可以定义只读类型

扩展类型

interface Hh {
    name?: string
    age?: number
    addr?: string
    phone: number
}

interface Ab extends Hh {
    EnglishName: string
}

const ab: Ab = {
    EnglishName: 'bunny',
    phone: 1888888888
}

interface Co {
    like: string
}

interface ABC extends Ab, Co {
    color:string

}

const abc: ABC = {
    EnglishName: 'bunny',
    like: 'paint',
    phone: 1222222222222,
    color:'red'
} 

交叉类型

interface Col {
    color: string
}

interface Cir {
    radius: number
}

type ColCir = Col & Cir

const cc: ColCir = {
    color: "yellow",
    radius: 99
}

//也可以用函数的方法
function draw(colcir: Col & Cir) {
    console.log(colcir.color);
    console.log(colcir.radius);
}

draw(
    {
        color: 'blue',
        radius: 88
    }
)

注意:接口和交叉类型的区别
接口可以通过extends来扩展 交叉类型通过&
如果我们来去实现一个类型的合并我们就可以使用interface来去实现类型>的定义了,如果是我们避免这个类型的冲突的话,那我们就选择使用type这个类型别名的方法来去定义不同的类型

泛型对象

// interface NumberBox{
//     contents:number
// }
// interface StringBox{
//     contents:string
// }
// interface BooleanBox{
//     contents:boolean
// }
// //用重载去实现
// function setContents(box:StringBox,newContents:string):void
// function setContents(box:NumberBox,newContents:number):void
// function setContents(box:BooleanBox,newContents:boolean):void
// function setContents(box:{contents:any},newContents:any){
//     box.contents=newContents
// }
// //泛型对象
// interface Box{
//     contents:Type
// }
// let boxA:Box={
//     contents:100
// }

// let boxB:Box={
//     contents:'bunny'
// }
// let boxC:Box={
//     contents:false
// }

// interface Box {
//     contents: Type
// }
// interface App {
//     //...
// }

// let a: App = {}
// type AppBox = Box
// let xx: AppBox = {
//     contents: a
// }

//使用类型别名定义泛型函数对象
type Box={
    contents:Type
}

type OrBull=Type|null
type OneOrMany=Type|Type[]

编写优秀通用函数的准则

1.可能的情况下,使用类型参数本身,而不是对其进行约束
2.总是尽可能少地使用类型参数
3.如果一个类型的参数只出现在一个地方,请从心考虑你师父真的需要它(这里我们要记住,类型参数是用来关联多个值的类型的,如果一个类型参数在函数签名中只使用一次,那么它是没有必要再次定义的)

编写好的重载

在可能的情况下,总是 倾向于使用联合类型的参数 而不是重载参数

了解其它类型

    1. void 表示没有返回值的函数的返回值,当一个函数没有任何返回语句或者没有从这个返回语句中返回任何明确的值的时候,它都是推断出来的

例:

function noop(){
    return;
}

return语句没有返回任何的值,那么typescript就会推断出返回类型是void,再javascript中一个不反悔任何值的函数将隐含的返回undefined这个值。但是,在typescript中,void和undefined是不一样的

    1. object 注意,这个类型o要小写
      object 指的是任何真基元的值:(string,number,bigint,boolean,symbol,null,undefined)

注意:小写的object不同于{},也不同于Object。

在javascript中,函数值是对象,它们有属性,比如他们在原型链里有Object.prototype是大写的Object的一个实例,可以对它调用,Object.key等。由于这个原因呢,typescript在函数类型上也有对应,我们就用这个小写的object。

注意:小写的object不是Object ,在ts里始终要使用object!

    1. unknown unknown类型表示任何值,这与any类型类似,但是更安全,因为对未知unknown值做任何事情都是不合法的。

例2:
function f1(a:any){
a.b();//正确(不管b是否存在都会正常)
}

function f2(a:unknown){
a.b();//报错,不能访问unknown类型
}

//例2:

function safeParse(s:string):unknown{
    return JSON.parse(s);
}

const obj=safeParse(someRandomString);
//需要小心对待'obj'!
    1. never never类型表示永远不会被观察到的值
function fail(msg:string):never{
    throw new Error(msg);
}

这个函数fail 它的返回值是一个异常,那么它的返回值的类型就是never了,这就意味这,如果一个函数抛出一个异常或终止一个程序的执行或者是死循环,那么它的返回值的类型就是never了,那never呢也出现在TypeScript确定一个联合类型union,没有任何东西的时候 我们再来看一个例子

function fn(x:string|number){
    if(typeof x==="string"){
        //dosomthing
    }else if(typeof x==="number"){
        //dosomething
    }else{
        x;//“never”
    }
}

函数fn 它的形参的类型是个联合类型,由string和number组成 我们在方法体里面来进行类型缩小,如果这个x的类型是string 。。。

    1. Function 全局性的Function类型描述了诸如bind、call、apply和存在于javascript中所有函数值的属性。它还有一个特殊的属性,即Function类型的值 总是可以被调用。这些调用返回any

你可能感兴趣的:(bunny笔记|TS基础(1):基础类型定义、回调签名、构造签名、泛型、重载函数等)