Typescript 基础知识

  TypeScript 简称:TS,是 JavaScript 的超集,简单来说就是:JS 有的 TS 都有。从编程语言的动静来区分,TypeScript 属于静态类型的编程语言,JavaScript 属于动态类型的编程语言,TS在编译期做类型检查,而JS是在执行期做类型检查,在这一点上来说,TS能更早的发现错误,这样可以减少我们查找、修改bug的时间。

        使用TS需要编译器,因为浏览器不认识TS,需要工具将TS转化为JS

        安装命令:npm i -g typescript 或者 yarn global add typescript

        验证是否安装成功:tsc –v


原始数据类型:number / string / boolean / null / undefined

  // number
  let a:number = 10

 // boolean
  let b:boolean = false

  // string
  let c:string = '1234'

  const d:undefined = undefined
  console.log(d)

  const e:null = null
  console.log(e)

其中,undefined 和 null 是所有类型的子类型,也就是说 undefined / null 类型的变量,可以赋值给 number 类型的变量,而 void 类型的变量不能赋值给 number 类型的变量

let n:string = null/undefined

空值:在 TypeScript 中,可以用 void 表示没有任何返回值的函数

 // void 空值,表示没有任何返回值的函数
 function fn1():void {
    console.log(123456)
 }
 console.log(fn1())
 

任意值:any

  // any 任何类型
 let h:any = 123
  h = null
  h = undefined
h = []
h = new String()
h = String
console.log(h)
let newARR:any[]=[,2,3,'',true]
console.log(newARR)

数组:有两种写法

  // Array
  let arr:number[] = [1, 2, 3]
  arr = [4, 5, 6]
  console.log(arr)
  const arr1:Array<number> = [7, 8, 9]
  console.log(arr1)

对象

  let obj:object = {}


类型推论:TS会在没有明确的指定类型的时候推测出一个类型

let g;

 

联合类型:表示取值可以为多种类型中的一种

 注意:当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法

接口:它是对行为的抽象,用于对「对象的形状(Shape)」进行描述,简单理解为一种约束

// 定义接口
interface IPerson{
    id:number,
    name: string,
    age: number,
}
    
let p:IPerson={
        id: 123321,
        name: 'tom',
        age: 19,
}

console.log(p)

 可以看到,p与IPerson的变量和变量的类型是一致的,因为我们约束了p的形状必须和接口IPerson一致,定义的变量比接口少了一些、多了一些属性是不允许的,会报错。

接口-可选属性:有时不想完全匹配一个形状,这时候可以使用可选属性,在定义的属性名后面机上?即可,这样在调用接口是不定义sex属性也不会报错。

sex?: string

但是这时候仍然不允许添加未定义的属性,比如接口里只有A、B、C三种属性,定义对象T时,T里面多出了个D属性,会直接报错。

接口-任意属性:有时想要一个任意属性在接口里,可以这么定义

[propName:string]:any

使用 [propName: string] 定义了任意属性取 string 类型的值,需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集,例如定义[propName:string]:string, 确定属性和可选属性只能定义为string的子集。

一个接口中只能定义一个任意属性。如果接口中有多个类型的属性,则可以在任意属性中使用联合类型:

[propName: string]: string | number;

接口-只读属性:希望某个属性只能只读,定义时在属性前加 readonly

readonly id:number,

也可以用接口来表示数组

interface INewArray{
    [index:number]:number
}

let arr2:INewArray = [1,2,3,4]

INewArray表示:只要索引的类型是数字时,那么值的类型必须是数字。


函数:JS中的函数定义方式

// 函数声明 命名函数
function add(a,b){
    return  a+b
}

// 函数表达式 匿名函数
let add = function(a,b){
    return a+b
}

TS中为:

function add(a:number, b:number):number{
    return a+b
}
let f:number=add(1,2)
console.log(f)

// 函数表达式 匿名函数
let add1=function(a:number, b:number):number{
    return a+b
}
let g:number=add1(3,4)
console.log(g)

完整写法为:

let add2:(a:number, b:number)=>number=function(a:number, b:number):number{
    return a+b
}
let j:number=add2(4,5)
console.log(j)

接口约束函数

// 约束函数就是约束函数的 参数与返回值
interface ISearchFunc{
    // (参数:类型,...):返回值的类型
    (a:string, b:string):boolean
}

const fun1:ISearchFunc=function(a:string, b:string):boolean{
    return a.search(b) !== -1
}

console.log(fun1('123', '2'))

上方约束了参数与返回值的类型

函数的可选参数和默认参数

// 可选参数 ?  必选参数不能放在可选参数后面
// 默认参数 不传值时使用默认参数定义的值,且可以放在必选参数和可选参数后面
let getName=function(x:string, y?:string, z:string='李'):string {
    return x + y + z
}
console.log(getName('张'))

可以看到,此时只传了一个参数进去,这个参数会被X接收,输出结果为

张undefined李 

这时再传另一个值进去(假设传了个字符串三),这个值会被可选属性Y接收,输出结果为

张三李

如果传了三个值进去,输出结果则为传的那三个值,而定义好的默认参数Z的值->李 会被顶掉

函数的剩余参数

function fn(a:string,b:string, ...args:number[]){
    console.log(a,b,args);
}
fn('','',1,2,3,4,5)

函数重载:重载允许一个函数接受不同数量或类型的参数时,作出不同的处理

假如定义一个函数,要求当传入数值时,输出结果为数值;传入字符串,输出结果为字符串,这时可以使用重载,提前定义好接收什么时输出什么。假如不使用重载,这时有可能x接收的是数值,y接收的是字符串,显然这不是想要的结果,因为这不能够精确地做出表达。

function newArr(x:string, y:string):string
function newArr(x:number, y:number):number

function newArr(x:string|number, y:string|number ):string|number|void{
    if (typeof x == 'string' && typeof y == 'string') {
        return x + y
    } else if (typeof x == 'number' && typeof y == 'number') {
        return x + y
    }
}
console.log(newArr(1,2));
console.log(newArr('张','两三百'));

注意,TypeScript 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。


类型断言:手动指定一个值的类型

语法:值 as 类型    或者  <类型>值

例子:定义一个函数,获取传入值(数字、字符串)的长度

// 一、将一个联合类型断言为其中一个类型
function getLength(x:string|number):number|void{
    // return x.toString().length
    if((x as string).length) {
        return (<string>x).length
    } else if (typeof x == 'number') {
        return x.toString().length
    }
}
console.log(getLength('asoidaonbf'));
console.log(getLength(123));
// 二、将任何一个类型断言为 any,any类型可以访问任何属性和任何方法
//  ===>  它极有可能掩盖了真正的类型错误,所以如果不是非常确定,就不要使用 as any
(window as any).l = 10
// 三、将 any 断言为一个具体的类型
function abc(x:any, y:any):any{
    return x + y
}

let l = abc(1,2) as number
// let l = abc('4','45') as string

断言总结:

  • 联合类型可以被断言为其中一个类型
  • 任何类型都可以被断言为 any
  • any 可以被断言为任何类型

类型别名:给一个类型起个新名字

// 类型别名 常用于联合类型
type snb = string | number | boolean
let abcd:snb = 123
let abcde:snb = '321'
let abcdef:snb = true
abcde=123

字符串字面量:用来约束取值只能是某几个字符串中的一个

type stringType = '123' | '456' | '789'
let names:stringType = '123'

元组

// 数组合并了相同类型的对象,而元组(Tuple)合并了不同类型的对象。
let arr3:number[]=[1,23,3.4]
let Tarr:[number, string] = [123, '456']
// 添加内容的时候, 只需要是元组里定义的类型即可,顺序不管
Tarr.push(111)
Tarr.push('222')

枚举:用于取值被限定在一定范围内的场景,枚举使用 enum 关键字来定义

// 可以理解为定义了一个类型为枚举的对象
enum NumberType{
    one=1, // (one=1) ==> 手动赋值,如果没有赋值的话(one),第一个参数默认为0,而后递增下去
    two,
    three,
    four
}
console.log(NumberType.one);

上面会被编译为这样的JS:

var NumberType;
(function (NumberType) {
    NumberType[NumberType["one"] = 1] = "one";
    NumberType[NumberType["two"] = 2] = "two";
    NumberType[NumberType["three"] = 3] = "three";
    NumberType[NumberType["four"] = 4] = "four";
})(NumberType || (NumberType = {}));
console.log(NumberType.one);

从被编译后文件可以得知:

可以通过名称获取到值,也可以通过值获取名称;

如果是按顺序手动赋值(递增),当给one赋值1,后面的可以不赋值,因为会自动递增+1

后一个值总会根据前一个值的值去递增,比如one给了1,two自动递增+1赋值为2,three赋值为10,four则会递增+1赋值位1;

注意:尽量不要赋同样的值,否则前一个值会被后一个值直接覆盖

枚举项:常数项和计算所得项

例如

enum Color{
    red,   // 常数项
    blue='blue'.length,  // 计算所得项
    green=11
}

blue需要通过计算’blue’的长度得来,所以blue是计算所得项

注意:计算所得项要放在已经确定赋值的枚举项之前,计算所得项后不要接未赋值的常数项,会报错,除非接上去的是手动赋值

常数枚举:使用 const enum 定义的枚举类型

const enum Obj{
    o,
    b,
    j
}
console.log(Obj.o);
console.log(Obj.b);
console.log(Obj.j);

它的JS编译结果为:

console.log(0 /* Obj.o */);
console.log(1 /* Obj.b */);
console.log(2 /* Obj.j */);

可以看出:常数枚举与普通枚举的区别是,它会在编译阶段被删除,并且不能包含计算成员

外部枚举: 使用 declare enum 定义的枚举类型

declare enum ABC{
    a,b,c
}
console.log(ABC.a);
console.log(ABC.a);

declare 定义的类型只会用于编译时的检查,编译结果中会被删除。

外部枚举同样也会在编译结果中被删除掉,但是输出的是原封不动的,例如在常数枚举中输出的结果是该枚举项的值,而在外部枚举中输出的是该枚举项,而不是枚举项的值

// 可以和const同时使用
declare const enum DEF{
    a,b,c
}
console.log(DEF.a);

:描述了所创建对象的 共同的属性和方法

在JS中实例化一个对象:

class Person{
    constructor(name, age){
        this.name = name
        this.age = age
    }
}
new Person()  // 在new的时候,会执行类中的构造方法constructor

typescript中:

class Person{
    name:string
    age:number
    constructor(name:string, age:number){
        this.name = name
        this.age = age
    }
    SayHi(str:string){
        console.log('hi,' + str);
        
    }
}
let pp = new Person('张三', 12) 
pp.SayHi('666')

注意:在ts中写属性,需要提前给属性定好是什么类型,才能在constructor中this添加对应的属性和方法

类的继承:通过继承,扩展现有的类

class Animal{
    name:string
    age:number
    constructor(name:string, age:number){
        this.name = name
        this.age = age
    }
    SayHi(str:string){
        console.log('hi,' + str);
        
    }
}

class Dog extends Animal{
    constructor(name:string, age:number){
        // 使用super,调用父类的构造函数constructor
        super(name, age)
    }
    // 可以调用父类的方法
    // SayHi(str:string){
    //     console.log('hi,' + str);
        
    // }
    // 也可以重写方法
    SayHi(){
        console.log('hi,我是dog类的边牧');
        // 也可以在这里调用父类的方法
        super.SayHi('一号')
    }
}

let dog = new Dog('边牧', 6)
dog.SayHi()

输出结果:

hi,我是dog类的边牧
hi,一号

类的存储器:控制对对象成员的访问

class Name{
    firstName:string
    lastName:string
    constructor(firstName:string, lastName:string){
        this.firstName = firstName
        this.lastName = lastName
    }
    // 读取器
    get fullName(){
        return this.firstName + '-' + this.lastName
        // return '张三丰'
    }
    // 设置器
    set fullName(value) {
        console.log(value);
        let names=value.split('-')
        this.firstName = names[0]
        this.lastName = names[1]
    }
}
const ppp = new Name('张', '二')
console.log(ppp);
ppp.fullName='李-大头'
console.log(ppp.fullName);

输出结果:

Name { firstName: '张', lastName: '二' }
李-大头
李-大头

类的静态方法:使用 static 修饰符修饰

// 静态属性 ===> 只属于类自己的属性和方法 --> 只能自己调用
class aa{
    static name1: string;
    // constructor(name:string){
    //     this.name = name
    // }
    static sayHi(){
        console.log('777');
    }
}
const aa1 = new aa()
console.log(aa.name1);
aa.sayHi()
// console.log(aa1.name1); 报错
// aa1.sayHi()  报错

类的修饰符:TypeScript 可以使用三种访问修饰符(Access Modifiers),分别是 publicprivate protected

public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的。

private 修饰的属性或方法是私有的,不能在声明它的类的外部访问。

protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的。

class B{
    private name1:string
    protected age:number
    public constructor(name1:string, age:number){
        this.name1=name1
        this.age=age
    }
    public p(){
        console.log('444');
        console.log(this.name1); // private name1 只能在类B里访问
        console.log(this.age);
        
    }
}
class C extends B{
    constructor(name1:string,age:number){
        super(name1,age)
    }
    play(){
        // console.log(this.name1);报错
        console.log(this.age); 
    }
}
const bb = new B('张三', 12)
// console.log(bb.name1); 报错->只能在类B里访问 ↑
bb.p()
const cc= new C('李四', 13)
cc.play()  // 父类共有的方法继承下来,子类可以使用
// console.log(cc.name1); 报错->只能在类B里访问 继承下来了但无法使用

父类使用了private定义的属性或方法,可以被子类继承,但子类内部无法访问,被private的属性只能被父类使用

readonly修饰符

class X{
    readonly age:number  // 只读属性,但是可以在构造函数里修改
    // 如果readonly以及三个修饰符定义在参数上,那就是创建并初始化这个参数
    constructor(age:number){
        this.age=age
    }
    update(){
        // this.age=12 报错
    }
}

类的类型:把类作为类型使用

class Car {
    name:string
    constructor(name:string) {
        this.name = name
    }
}

class Ben extends Car {
    constructor(name:string) {
        super(name)
    }
}

const ben : Car = new Ben('')

注意:子类 与 父类 需要有同样的属性 如果子类出现父类没有的属性 则父类不能被当成类型使用

类实现接口:使用 implements 关键字来实现

interface ISing {
    sing():any
}
interface IDance {
    dance():any
}

class pppp implements ISing, IDance{
    sing(){
        console.log('唱歌');
    }
    dance() {
        console.log('跳舞');
    }
}

class an implements ISing, IDance{
    sing(){
        console.log('唱歌');
    }
    dance() {
        console.log('跳舞');
    }
}

const p1 = new pppp()
p1.sing()
p1.dance()
const p2 = new an()
p2.sing()
p2.dance()

接口继承接口

// 接口可以继承其他多个接口
interface IRun {
    run():any
}
interface ISwim {
    swim():any
}
interface IActive extends IRun,ISwim{

}

class I implements IActive {
    run() {
        console.log("I在run");
    }
    swim () {
        console.log('I在swim');
    }
}

const ii = new I()
ii.run()
ii.swim()

接口继承类

class newPerson {
    name:string
    constructor(name:string){
        this.name = name
    }
    sayHi(){
        console.log(123);
        
    }
}

interface IPeerson extends newPerson { // 接口继承类中的实例属性和实例方法
    age:number
}

let person:IPeerson = {
    name: '',
    age: 12,
    sayHi() {
        console.log(456);
    }
}

person.sayHi()

接口合并

// 相同属性的合并,类型需一致
interface cat{
    name: string
    age:number
}

interface cat {
    name:string
    gender:string
}

const catt:cat =  {
    name: '橘猫',
    age: 12,
    gender: '女'
}

泛型:指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。

不使用泛型:

// 需求:定义一个函数,传入两个参数,第一个参数是数据,第二个参数是数量
// 函数的作用:根据数量产生对应数量的数据,存放在数组中
function getArr(value:number, count:number):number[]{
    const arr = []
    for(let i = 0; i < count; i++){
        arr.push(value)
    }
    return arr
}
console.log(getArr(123, 4));

使用泛型:

// 使用泛型,定义时不需要先确定类型,使用的时候才来确定

// T 表示任意输入的类型
function getArr<T>(value:T, count:number):T[]{
    const arr:T[] = []
    for(let i = 0; i < count; i++){
        arr.push(value)
    }
    return arr
}
console.log(getArr(123, 4)); // 不写类型 -> <number>时 会自己做类型推断
console.log(getArr<string>('321', 5));

输出结果:

[ 123, 123, 123, 123 ]
[ '321', '321', '321', '321', '321' ]

多泛型参数的函数

function updateArr<T, U>(t: [T, U]): [U, T] {
    return [t[1], t[0]]
}
console.log(updateArr<string, number>(['123', 123]));
console.log(updateArr<boolean, number>([true, 123]));

泛型约束:约束泛型必须包含某种东西。

例如,约束输入的类型,必须要有length属性:

interface ILength{
    length:number
}
function getArrLength<T extends ILength>(x:T):number{
    return x.length
}
console.log(getArrLength('123'));

假如这时传入的值不包含length属性,例如数值123,就会报错。

泛型接口:使用含有泛型的接口来定义函数的形状

interface IArr {
    <T>(value: T, count: number):Array<T> 
}

let getArr1:IArr = function <T>(value:T, count:number):T[]{
    const arr:T[] = []
    for(let i = 0; i < count; i++){
        arr.push(value)
    }
    return arr
}
console.log(getArr1('333', 7));

泛型类

class Pperson<T>{
    name:string
    age: T
    constructor(name:string, age:T){
        this.name = name
        this.age = age
    }
}
const pperson = new Pperson<number>('123', 456)
const pperson1 = new Pperson<string>('123', '456')