开启ts学习之路,本篇ts的ES6面向对象、接口类型和泛型。
ES5的面向对象是由函数实现,加上TS的话按照之前内容即可。而ES6的面向对象是使用class来定义类,因此需要特殊讲一下:
以下描述一个Animal类:
class Animal {
name: string; // 声明了Animal类中的name属性必须为string类型
constructor(name: string){
this.name = name;
}
sayName():void{ // 加void表示没有返回值
console.log(this.name);
}
}
let animal = new Animal("小瑶");
animal.sayName(); // 小瑶
定义一个Cat类,继承自Animal类:
class Animal {
name: string; // 声明了Animal类中的name属性必须为string类型
constructor(name: string){
this.name = name;
}
sayName():void{
console.log(this.name);
}
}
class Dog extends Animal {
constructor(name: string){
super(name);
}
shout(): void{
console.log(`${this.name}正在“汪汪”`);
}
}
let dog = new Dog("小瑶");
dog.shout(); // 小瑶正在喊“汪汪”
类属性和方法除了可以通过 extends 被继承之外,还可以通过修饰符控制可访问性。
在 TypeScript 中就支持 3 种访问修饰符,分别是 public、private、protected。
public 修饰的是在任何地方可见、公有的属性或方法。上述代码中,凡是没有加修饰符的,都是默认已经自带public:
class Animal {
name: string;
constructor(name: string){
this.name = name;
}
sayName():void{
console.log(this.name);
}
}
let animal = new Animal("张三");
animal.sayName();
// 以上代码相当于:
class Animal {
public name: string;
constructor(name: string){
this.name = name;
}
public sayName():void{
console.log(this.name);
}
}
let animal = new Animal("张三");
animal.sayName();
private 修饰的是仅在当前类中可见、私有的属性或方法。具体如下:
class Animal {
public name: string;
private age: number; // 只能在当前类中使用,实例和子类都不能用
constructor(name: string, age: number){
this.name = name;
this.age = age;
}
public sayName():void{
console.log(`${this.name}今年${this.age}岁`);
}
}
let animal = new Animal("张三", 12);
animal.sayName(); // 张三今年12岁
console.log(animal.age); // 属性“age”为私有属性,只能在类“Animal”中访问 ✖
protected 修饰的是仅在类自身及子类中可见、受保护的属性或方法。
class Animal {
public name: string;
protected age: number; // 只能在当前类和子类中使用,实例不能用
constructor(name: string, age: number){
this.name = name;
this.age = age;
}
}
let animal = new Animal("张三", 11);
console.log(animal.age); // 属性“age”受保护,只能在类“Animal”及其子类中访问 ✖
class Cat extends Animal {
constructor(name: string, age: number){
super(name, age);
}
shout(): void{
console.log(`${this.name}正在喊“喵喵”,它今年${this.age}岁`); // 可以使用 ✔
}
}
let cat = new Cat("嘟嘟", 11);
cat.shout(); // 嘟嘟
加上readonly的属性只能读不能写:
加上static的属性与方法,无需实例化即可调用:
class Animal {
// 直接给静态属性赋值
static username: string = "张三";
}
Animal.username; // 张三
简单一句话:抽象类无法被实例化。
// 定义一个抽象类
abstract class Animal {
name: string;
constructor(name:string){
this.name = name;
}
}
let animal = new Animal("张三"); // 无法创建抽象类的实例。
请先看这段js代码:
function fn({name, age}){
console.log(`${name}的年龄为:${age}`)
}
fn({name: "张三", age: 18}); // 张三的年龄为:18
它的作用是给函数传了个对象,并且把对象中两个字段解构出来使用。但如果你要指定两个字段的类型,就会报错了:
此时如果你通过以下代码书写,效果就不一样了:
function fn({name, age}: {name: string; age: number}){
console.log(`${name}的年龄为:${age}`)
}
fn({name: "张三", age: 18}); // 张三的年龄为:18
其实这段代码是接口类型的演变,原来的表现形式应该如此:
// 使用接口定义字段类型
// 接口一般首字母大写,经常使用I开头
interface IObj {
name: string;
age: number;
}
function fn(obj: IObj): void{
console.log(`${obj.name}今年${obj.age}岁`)
}
fn({name: "张三", age: 18}); // 张三今年18岁
以上接口中,name和age的顺序是可以打乱的,不会影响代码执行。
接口中定义的字段,参数必须传,但有些参数我们希望可传可不传,可以如下:
interface IObj {
name: string;
age?: number; // ?表示该参数可传可不传
}
function fn(obj: IObj): void{
console.log(`${obj.name}今年${obj.age || 0}岁`)
}
fn({name: "张三"}) // 张三今年0岁
其实属性名称有时也不确定,因此可以使用以下方式定义,但值必须为any。
interface IObj {
name: string;
// [propName: string]: string | number;
// 这个参数名称可能也是不确定的,也不清楚它的类型,可以any,也可指定若干数据类型,建议直接any
[propName: string]: any;
}
function fn(obj: IObj): void{
console.log(`${obj.name}是${obj.sex || '男'}性`)
}
fn({name: "张三"}) // 张三是男性
fn({name: "张三", sex: "女"}) // 张三是女性
注意:
任意属性和可选属性最好不能共存,即便共存最好类型一致。
泛型是指在定义函数、接口,或者类的时候,不预先指定具体的类型,而是在使用的时候在指定类型的一种特性。
现在有一个需求:有一个函数可以创建指定长度的数组,每一项都要填充一个指定值。
function createArr(length: number, value: any): Array<any>{
// 或:function createArr(length: number, value: any): any[]{
let arr = [];
for(var i=0;i<length;i++){
arr[i] = value;
}
return arr;
}
console.log( createArr(3, "猴子的救兵") ); // [ '猴子的救兵', '猴子的救兵', '猴子的救兵' ]
以上代码在函数调用前,是不清楚返回的数据类型的,因为value的数据类型为any。但我们说尽量不使用any,毕竟它绕过了类型检查,因此,我们需要借助泛型来解决。
一般我们用 T
来代表输入和返回的数据类型,直到调用了才明确 T
是什么数据类型:
function createArr<T>(length: number, value: T): Array<T>{
// 或:function createArr(length: number, value: T): T[]{
let arr: T[] = [];
for(var i=0;i<length;i++){
arr[i] = value;
}
return arr;
}
console.log( createArr<string>(3, "猴子的救兵") ); // [ '猴子的救兵', '猴子的救兵', '猴子的救兵' ]
如果有多个参数都是类型未知的,那么泛型应该这么写:
// 情况一:多个参数传入
function fn<U, T>(flag: U, value: T): Array<U | T>{
let arr: Array<U | T> = [flag, value];
return arr;
}
console.log(fn(true, "我是孙悟空")) // [ true, '我是孙悟空' ]
// 情况二:单个参数传入,且为数组
function fn<U, T>(arr: [U, T]): Array<U | T>{
let newArr: Array<U | T> = [...arr];
return newArr;
}
console.log(fn([true, "我是孙悟空"])) // [ true, '我是孙悟空' ]