元组
在TypeScript中,有元组的概念,这个概念的js实现是数组,所以可以理解为是有不同类型对象的数组。
let a:[number,string];
a = [55,'25']
这样是元组的基本用法,有基本用法,那就一定有奇怪的用法。
我们可以通过一定方法对任意一个进行赋值,而不管另一个
let a:[number,string];
a[1] = '25'
但是我们不可以在对a直接赋值的时候,给出不符合规则的值
let a:[number,string] = 55;
这样是无法通过编译的。
既然是数组了,那么可不可以继续添加对象进去呢?当然是可以的,但是需要是规定的类型才行,比如上面我们规定元组的第一个元素是number类型,第二个是string类型,那么后续的只能是number、string或者他们的子类型。
字符串字面量类型和枚举类型
这两个为什么要放到一起说呢?因为他们都限定了在这个类型内,可以选择的字符串只有有限个,所以两个放到一起说一下。
字符串字面量类型
这个类型特别简单,就是在自己设定的类型中,只有有自己设定的值。
type Name = 'xiaoming' | 'xiaohong' | 'xiaoliang';
function call(name: Name){
//call
}
call('xiaoming'); //可以编译
call('wo'); //不能编译
这就是字符串字面量类型,没有任何变化。
枚举
枚举类型被用于取值限定在一定范围内的场景,用途类似上面的字符串字面量类型。
enum Week {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
console.log(Days["Sun"] === 0); // true
console.log(Days[0] === "Sun"); // true
这样就是一个枚举类型了,他在js的实现是
var Week;
(function (Week) {
Week[Week["Sun"] = 0] = "Sun";
Week[Week["Mon"] = 1] = "Mon";
Week[Week["Tue"] = 2] = "Tue";
Week[Week["Wed"] = 3] = "Wed";
Week[Week["Thu"] = 4] = "Thu";
Week[Week["Fri"] = 5] = "Fri";
Week[Week["Sat"] = 6] = "Sat";
})(Week || (Week = {}));
这个实现没什么好说的,看一眼应该就知道他的原理是怎么回事了,如果觉得有点抽象,可以再浏览器里打印一下Week这个对象,就明白这里的枚举类型是什么意思了。
枚举类型还可以手动赋值
enum Week {Sun = 7, Mon = 1, Tue, Wed, Thu, Fri, Sat};
console.log(Days["Sun"] === 7); // true
console.log(Days["Tue"] === 2); // true
枚举类型的需要会按照以最后一个设定值为基础,每一个值+1的顺序往下排,即使最后一个设定值不为整数也是一样。
还可以用断言添加非数值的序数,但是这个断言后面的所有序数都要手动设置了。
enum Days {Sun = 7, Mon, Tue, Wed, Thu, Fri, Sat = <any>"a"};
除了固定值的序号,还可以用计算量做需要,但是计算量和字符串序号一样,后面都不能有未设定序号了。
类
在es6之前,JavaScript没有类的概念,但是在es6中,出现了类,同样TypeScript中也有对类的规定,这里的规定很像java中类的规则。
修饰符
TypeScript中有三个修饰符,分别是public,private,protected。
public是公开的,可任意访问的,
private是私有的,只能在类内部访问,他的子类和外部是不能访问的。
protected是受保护的,同样不能再外部访问,但是可以再子类和内部访问。
class Animal {
public name;
public constructor(name) {
this.name = name;
}
}
let a = new Animal('Cat');
console.log(a.name); // Cat
这样的访问是没有问题的
class Animal {
private name;
public constructor(name) {
this.name = name;
}
}
let a = new Animal('Cat');
console.log(a.name);
这样编译就会报错,因为私有属性是不可以在外部被访问的。
class Animal {
protected name;
public constructor(name) {
this.name = name;
}
}
class Cat extends Animal {
constructor(name) {
super(name);
console.log(this.name);
}
}
受保护的属性是可以再子类中被访问,但是不能在外部访问。
抽象类
所谓抽象类,就是不能实例化的类,这种类就是为了子类而存在的,他们的子类可以实例化。
抽象类还有抽象方法,抽象方法可以再抽象类中定义,不去做实现,在子类中必须实现抽象方法。关键字是abstract
abstract class Animal {
public name;
public constructor(name) {
this.name = name;
}
public abstract sayHi();
}
class Cat extends Animal {
public eat() {
console.log(`${this.name} is eating.`);
}
}
let cat = new Cat('Tom');
这样会报错,因为还有一个抽象方法没有写实现。
类与接口
接口的实质是对象的形状,那么接口可以规定对象,可以规定类吗?
答案是啊可以的
interface Alarm {
alert();
}
class Door {
}
class SecurityDoor extends Door implements Alarm {
alert() {
console.log('SecurityDoor alert');
}
}
class Car implements Alarm {
alert() {
console.log('Car alert');
}
}
这里抽象出来一个接口,叫做警铃。门有门铃功能,车有警铃功能,那么把这两个类共有的东西抽象出来,就是类的接口。
接口规定,这个类必须有这个属性,至于能不能有其他的东西,接口不管。如果类可以抽象出来多个接口,那么也可以使用多个接口规定同一个类
interface Alarm {
alert();
}
interface Light {
lightOn();
lightOff();
}
class Car implements Alarm, Light {
alert() {
console.log('Car alert');
}
lightOn() {
console.log('Car light on');
}
lightOff() {
console.log('Car light off');
}
}
接口还有一种很特殊的继承性,子接口可以继承父接口的规则。
interface Alarm {
alert();
}
interface LightableAlarm extends Alarm {
lightOn();
lightOff();
}
个人不是很喜欢这种继承,确实可以再项目扩大之后,写的更加简略比如上面的
class Car implements Alarm, Light {
alert() {
console.log('Car alert');
}
lightOn() {
console.log('Car light on');
}
lightOff() {
console.log('Car light off');
}
}
此时可以简写为
class Car implements Light {
alert() {
console.log('Car alert');
}
lightOn() {
console.log('Car light on');
}
lightOff() {
console.log('Car light off');
}
}
但是这时如果Light是多层的嵌套的时候,你就会很疑惑他的规则到底是什么。想要查清楚需要追溯到最上面一层,就好像一个树,你得看完他所有节点才能清楚的知道全部,对于ts的检查来说自然无所谓,但是对于写代码的人来说,就很烦,尤其是在没有完善的文档的情况下。
接口还可以直接从类里继承
class Point {
x: number;
y: number;
}
interface Point3d extends Point {
z: number;
}
let point3d: Point3d = {x: 1, y: 2, z: 3};
对这种东西,个人表示也不太喜欢,理由基本同上。