传统方法中,JavaScript
通过构造函数实现类的概念,通过原型链实现集成。而在ES6中,我们终于迎来了class
。
TypeScript 除了实现了所有ES6的类的功能以外,还添加了一些新的用法。
虽然 JavaScript中有类的概念,但是可能大多数 JavaScript程序员并不是非常熟悉类,这里对类相关的概念做一个简单的介绍。
new
生成Cat
和Dog
都继承自Animal
,但是分别实现了自己的eat
方法。此时针对某一个实例,我们无需了解它是Cat
还是Dog
,就可以直接调用eat
方法,程序会自动判断出来应该如何执行eat
。public
表示公有属性或方法。使用class
定义类,使用constructor
定义构造函数。
通过new
生成新实例的时候,会自动调用构造函数。
class Animal{
public name
constructor(name){
this.name = name
}
sayHi(){
return `My name is ${this.name}`
}
}
let a = new Animal('Jack');
console.log(a.sayHi())
使用extends
关键字实现继承,子类中使用super
关键字来调用父类的构造函数和方法:
class Cat extends Animal {
constructor(name) {
super(name)
}
sayHi() {
return `Meow,` + super.sayHi();
}
}
let c = new Cat('Tom'); // Tom
console.log(c.sayHi()); // Meow, My name is Tom
使用getter
和setter
可以改变属性的赋值和读取行为:
class Animal {
constructor(name) {
this.name = name;
}
get name() {
return "jack"
}
set name(value) {
console.log('setter:' + value)
}
}
let a = new Animal('Kitty'); // setter: Kitty
a.name = 'Tom'; // setter: Tom
console.log(a.name); // Jack
使用static
修饰的方法被称为静态方法,他们不需要实例化,而是直接通过类来调用:
class Animal{
public name
static isAnimal(a){
return a instanceof Animal
}
constructor(name) {
this.name = name;
}
}
let a = new Animal('Jack');
Animal.isAnimal(a);
ES7中有一些关于类的提案,TypeScript也实现了他们,这里做一个简单的介绍。
ES6中实例属性只能通过构造函数中的this.xxx
来定义,ES7提案中可以直接类型里面定义:
class Animal{
name='jack'
constructor(){}
}
let a = new Animal();
console.log(a.name); // Jack
ES7提案中,可以使用static
定义一个静态属性:
class Animal {
static num = 42
constructor(){}
}
console.log(Animal.num)
TypeScript可以使用三种访问修饰符(Access Modifiers),分别是public
、private
和protected
。
public
修饰的属性或者方法是公有的,可以在任何地方被访问到,默认所有属性和方法都是public
的private
修饰的属性或方法是私有的,不能在声明它的类的外部访问protected
修饰的属性或者方法是受保护的,他和private
类似,区别是他在子类中也是允许被访问的。 class Animal {
public name
public constructor(name) {
this.name = name
}
}
let a = new Animal('Jack');
console.log(a.name); // Jack
a.name = 'Tom';
console.log(a.name); // Tom
上面的例子中,name
被设置为了public
,所以直接访问实例的name
属性是允许的。
很多时候,我们希望有的属性是无法直接存取的,这时候就可以使用private
le :
class Animal {
private name
public constructor(name) {
this.name = name
}
}
let a = new Animal('Jack');
console.log(a.name); // 属性“name”为私有属性,只能在类“Animal”中访问。
a.name = 'Tom'; // 属性“name”为私有属性,只能在类“Animal”中访问。
console.log(a.name); // 属性“name”为私有属性,只能在类“Animal”中访问。
需要注意的是,TypeScript编译之后的代码中,并没有限制private
属性在外部的可访问性。
编译结果:
var Animal_7 = /** @class */ (function () {
function Animal(name) {
this.name = name;
}
return Animal;
}());
var a = new Animal_7('Jack');
使用private
修饰的属性或方法,在子类中也是不允许访问的:
class Cat extends Animal {
constructor(name) {
super(name);
console.log(this.name); // 属性“name”为私有属性,只能在类“Animal”中访问。
}
}
而如果是用protected
修饰,则允许在子类中访问:
class Animal {
protected name
public constructor(name) {
this.name = name
}
}
class Cat extends Animal {
constructor(name) {
super(name);
console.log(this.name);
}
}
当构造函数修饰为private
时,该类不允许被继承或者实例化:
class Animal {
private constructor(name) {
}
}
let a = new Animal('Jack');//类“Animal”的构造函数是私有的,仅可在类声明中访问。
class Cat extends Animal {
constructor(name) {
super(name);
}
} // 无法扩展类“Animal”。类构造函数标记为私有。
当构造函数修饰符为protected
的时候,改类只允许被继承:
class Animal {
protected constructor(name) {
}
}
let a = new Animal('Jack');//类“Animal”的构造函数是受保护的,仅可在类声明中访问。
class Cat extends Animal {
constructor(name) {
super(name);
}
}
let c = new Cat('c')
修饰符和readonly
还可以使用在构造函数的参数中,等同于类中定义该属性同时给该属性复制,使代码更简洁。
class Animal{
public constructor(public name){}
}
编译结果:
var Animal_9 = /** @class */ (function () {
function Animal(name) {
this.name = name;
}
return Animal;
}());
只读属性关键字,只允许出现在属性声明或索引签名或构造函数中。
class Animal{
readonly name
constructor(name){
this.name = name ;
}
}
let a = new Animal('A');
console.log(a.name)
a.name ="a" // 无法分配到 "name" ,因为它是只读属性。
注意,如果readonly
和其他访问修饰符同时存在的话,需要写在其后面。
class Animal {
// public readonly name;
public constructor(public readonly name) {
// this.name = name;
}
}
abstract
用于定义抽象类和其中的抽象方法。
什么是抽象类?
首先,抽象类是不允许被实例化的:
abstract class Animal{
constructor(){}
public abstract sayHi();
}
let a = new Animal() // 无法创建抽象类的实例。
上面例子中,我们定义了一个抽象类Animal
,并且定义了一个抽象方法sayHi
。在实例化抽闲类的时候报错了。
其次,抽闲类中的抽象方法必须被子类实现:
class Cat extends Animal{
public eat(){}
} // 非抽象类“Cat”不会实现继承自“Animal”类的抽象成员“sayHi”。
let c = new Cat();
正常使用:
class Cat extends Animal{
public eat(){}
sayHi(){}
}
let c = new Cat();
上面的例子中,我们实现了抽闲方法sayHi
,编译通过了。
需要注意的是,即使是抽象方法,TypeScript的编译结果中,仍然会存在这个类,上面的代码的编译结果是:
var Animal_11 = /** @class */ (function () {
function Animal() {
}
return Animal;
}());
// let a = new Animal() // 无法创建抽象类的实例。
var Cat = /** @class */ (function (_super) {
__extends(Cat, _super);
function Cat() {
return _super !== null && _super.apply(this, arguments) || this;
}
Cat.prototype.eat = function () { };
Cat.prototype.sayHi = function () { };
return Cat;
}(Animal_11)); // 非抽象类“Cat”不会实现继承自“Animal”类的抽象成员“sayHi”。
var c = new Cat();
给类加上 TypeScript的类型很简单,与接口类似:
class Animal{
name:string;
constructor(name:string){
this.name = name;
}
sayHi():string{
return `My name is ${this.name}`
}
}
let a:Animal = new Animal('Jack');