时隔多日,今天继续来分享一点ts的知识,感觉这个差点就被我给鸽了。本以为这一篇能把我理解的ts讲完,没想到还是没有讲完。今天先更新这一些吧。欢迎小伙伴继续来看看。
1.接口和类
这个概念很重要,居然被我之前忽略了。不好意思,现在来正式介绍一下。
接口(interface)和类(class)在强类型语言中Java中是很常见的,我本人有幸学过Java,对它们也还保留着一点认知。对于js,class我还算熟悉,interface就完全闻所未闻了。
如果让我用自己的话来描述它们,是这样的——“类是一个抽象概念,描述了一类东西——它们具有什么属性,什么方法。类的实例是对象,对象才是具体的一个东西,类只是表示一种类型。通过类,可以实现封装、继承和多态。
接口与对象类似,也是用来定义一种类型,但更侧重于是一个契约——一个对象实现了一个接口,就必须定义接口里的所有方法和成员”
现在我们来看看ts中如何定义接口。可以参考下面这一段代码——
interface Person {name: string;// 可选属性,到时候可以没有age?: number;// readonly关键字readonly id: number;
}let Betty: Person = {name: 'Betty Nangong',age: 24,id: 354200
}
如上述代码所示,interface关键字后面加上接口名,后面使用一对花括号即可定义一个接口,里面的内容就是接口的要求,用“属性名: 类型”来定义每个属性。
有这几点需要注意:①注意属性之间用分号而不是逗号隔开;②如果接口里定义了方法,则这里只写出函数的类型,不能在这里实现它;③如果遇到不一定需要设置的属性,则在“:”前加“?”来定义可选属性;④如果遇到后续不可更改的自读属性,可以在属性名前面加上readonly关键字。
定义好接口后,接口名称就代表了这种类型,它可以作为一个对象的类型。这时这个对象就会受到接口的制约——缺少非可选属性,给readonly属性重新赋值都会报错。
接口在ts中有两种作用:1. 用于描述对象的形状——也就是描述一个符合它要求的对象,得具有什么类型的什么属性。2.有“鸭子类型”的作用——“如果一个东西看起来像鸭子,会像鸭子那样叫,走路像鸭子,那么就可以把它当成鸭子”,鸭子类型在弱类型语言来说是比较灵活的,我的理解是,如果有两个对象,它们是不同的类的实例,但它们的各自的类如果实现了同一个接口,则从某种意义来说,可以把它们都当成同一类东西来一起处理。
类对于学过ES6的同学们来说就比较熟悉了。来,我们看一段示例代码——
// 定义动物类(基类)
class Animal {// 定义属性是属性名:类型readonly name: string;// 类的构造函数constructor(name) {this.name = name}// 这个类的实例的共有方法// 如果在父类的方法这里加上private,则子类无法使用它// 如果用protected修饰父类方法,则子类可以使用,但子类实例不可以使用run() {return `${this.name} is Running!`}
}// 创建一个动物实例
const snack = new Animal('lily')
console.log(snack.run())// 类的继承,extends关键字来继承父类
class Dog extends Animal {// 自己的狗叫方法bark() {return `${this.name} is barking`}
}// 创建子类实例
const xiaobao = new Dog('xiaobao')
// 子类实例可以调用父类和自己的方法
console.log(xiaobao.run())
console.log(xiaobao.bark())// 创建一个猫类
class Cat extends Animal {// 定义静态属性,这样可以写在ts里,但是js会报错static categories = ['mammal']// 重写run,返回值这里拼接了父类的run返回值myRun() {// 如果父类的方法加上了protected,则子类内部可以用,外部不能用return `Meow, ` + super.run()}// 重写构造函数,里面必须要先调用父类构造函数,然后才能用自己的this做别的事情constructor(name) {super(name);console.log(this.name)}
}const maoamo = new Cat('maomao')
console.log(maoamo.myRun())
console.log(Cat.categories)
可以看到,ts的class与ES6中的class语法上并没有明显的不同,一样是在花括号里面定义方法,用constructor作为构造函数,使用super来取到父类的原型对象,在构造函数中用super来调用父类构造函数,继承也是用extends关键字。ts的class与ES6的class又有什么不同之处呢?
我们老师说,ES6的语法让js更接近于Java了。而我觉得,ts的语法与Java语法又近了一大步。我以前在Java中学过的public、private和protected关键字在ts的class中又出现了。
简单介绍一下这三个关键字:1.public关键字表示这个属性在哪里都可以使用,默认的属性都是public的;2.private关键字表示这个属性是私有的,只能在自己这个class里面用,子类不可以用,自己的实例也不可以用;3.protected关键字表示只能在自己和子类中使用,自己的实例和子类的实例不可用(子类在定义方法的时候可以调用,但是不可以让子类实例在外面调用,假如上面的代码中在Animal类的run方法加上了protected,则除了Cat类的实例没有报错,Animal类和Dog类的实例这边都报错了)
另外,在js中并不能在类的内部用static关键字定义静态属性(我试过了,一定义就报错),在ts中的类里面是可以的。参考上面的代码。
2.类型推论(Type Inference)
这是一个简单的知识,看起来理所当然,实际上又很有用。
前面讲了一些能作为“类型”东西,包括基本的类型,特殊的类型“字面量类型”,还有自己定义的类型,如“函数的类型”,“类”,“接口”,甚至还有“联合类型”和“类型的别名”。那么问题来了,假如我在定义变量的时候不指定以上任意一个形式的类型,那这个变量是什么类型呢?
TypeScript的官网中介绍了“Type Inference”这个特性,中文意思是“类型推论”。这个特性的作用是,如果一个变量没有指定类型,ts就会根据它当前的值的类型来作为它的类型。例如,定义了一个变量后初始化时赋值为5,那么这个变量从此就成了数字类型,不可以被随意赋值为任意类型;假如定义变量时并没有初始化,则变量的类型为any类型;当然,null和undefined作为特殊的值,被视为任意类型的子类型,是可以被任意赋值的。
我个人认为,这个特性真的很棒,可以不强制定义变量时指定类型,这样可以完美兼容js代码的语法,让初学者能更好接受一些;另外,表面上看起来没有指定类型,实际上又暗戳戳地提供了类型,为我们避免了一些类型上的错误,为我们的代码保驾护航。
3.类型断言(Type Assertion)
这个特性的中文名字看起来跟前面那个有点像哈,但使用起来是完全不同的。类型断言适合跟联合类型一起使用。
简单解释一下什么是类型断言——假如你定义了一个函数,这个函数的某个参数类型不唯一,你让它的类型为“a类型和b类型的联合类型”。在函数里,当你要使用a类型里的某个方法(b类型没有)的时候,ts会无情地报错,因为它不确定这个参数是否有你要的方法,也就是ts不确定它是否为a类型。这个时候需要使用断言,使用as关键字,把这个参数断言成a类型,这样就可以使用a类型的方法了。注意断言的类型只能是那个联合类型里有的类型,不可以随意断言。
// 一个返回长度的函数,参数是一个联合类型的值,但是要访问的length却不是共有属性
function getLength(input: number | string): number {// 类型断言——使用as关键字,让服务器把input当成string来看const str = input as string// 如果我传入的是字符串,那么就有length属性,就直接返回length属性if (str.length) {return str.length}else {// 否则我传入的是数字,数字类型的先转成字符串,再返回lengthconst number = input as numberreturn number.toString().length}
}
4.类型守卫(Type Guard)
类型守卫也是一个为我们的类型保驾护航的特性。简单地说,就是用if判断变量的类型,在if(条件)后面的语句块里,就可以把这个变量当做这个类型来使用。
// 一个返回长度的函数,参数是一个联合类型的值,但是要访问的length却不是共有属性
function getLength2(input: number | string): number {// 还可以使用类型守卫,用typeof来获取这个参数实际的类型是哪种,用if进行判断if (typeof input === 'string') {return input.length} else {// 否则就肯定是数字类型了return input.toString().length}
}
如上面的代码,参数可能是数字或字符串类型。只有字符串类型才有length属性,这里用if来判断变量的类型,确定typeof为“string”后就可以把参数当成字符串使用,可以使用length属性了,else这里则想法,这里的参数肯定是数字类型的。
如果参数是可选的对象,可以把参数写成某类型(用接口或类定义的)与undefined类型的联合类型。在if判断时,发现这个参数是true即可证明它不是undefined,那么它就是你要的类型。
今天就先到这里啦,还有交叉类型,声明文件,泛型还没讲,应该要留到下一篇来说,目测下一篇会是最后一篇了。我写的东西都是个人的感悟,感谢大家的支持,欢迎指出我的问题和意见。