ES6的class实际上是原型继承被底层隐藏起来了
如何使用ES5代码创建类:
// 飞机的构造函数
function Plane(numEngines) {
this.numEngines = numEngines;
this.enginesActive = false;
}
// 所有实例继承的方法
Plane.prototype.startEngins = function(){
console.log('starting engins...');
this.enginesActive = true;
}
const johnsonsPlane = new Plane(1);
johnsonsPlane.startEngines();
const tammysPlane = new Plane(4);
tammysPlane.startEngines();
Plane
是一个构造函数,它将用来创建新的Plane
对象,每个对象都可以访问Plane.prototype
原型对象上的方法,这里的new
有四个步骤:
- 创建一个新的对象;
- 将构造函数的作用域赋给新的对象;
- 执行构造函数中的代码;
- 返回这个新的对象;
ES6在底层帮你设置了所有的这些,用ES6重写:
class Plane {
constructor (numEngines){
this.numEngines = numEngines;
this.enginesActive = false;
}
startEngins(){
console.log('starting engines...');
this.enginesActive = true;
}
}
类只是一种函数,在类种,不用逗号来区分属性或方法,如果添加逗号,将会出现语法错误
静态方法
要添加静态方法,在方法前加关键字static
class Plane {
constructor (numEngines){
this.numEngines = numEngines;
this.enginesActive = false;
}
// 静态方法,可直接调用Plane.badWeather([plane1, plane2])
static badWeather(planes){
for (plane of planes){
plane.enginesActive = false;
}
}
startEngins(){
console.log('starting engines...');
this.enginesActive = true;
}
}
ES6 中的子类
我们使用super
和extends
关键字扩展类,super必须在this之前被调用!
class Animal {
name: string;
constructor(theName: string) { this.name = theName; }
move(distanceInMeters: number = 0) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
class Snake extends Animal {
constructor(name: string) { super(name); }
move(distanceInMeters = 5) {
console.log("Slithering...");
super.move(distanceInMeters);
}
}
class Horse extends Animal {
constructor(name: string) { super(name); }
move(distanceInMeters = 45) {
console.log("Galloping...");
super.move(distanceInMeters);
}
}
let sam = new Snake("Sammy the Python");
let tom: Animal = new Horse("Tommy the Palomino");
sam.move();
tom.move(34);
类从基类中继承了属性和方法,Snake是派生类,它派生自Animal基类,通过extends
关键字,派生类称为子类,基类称为超类。
子类包含了一个构造函数,它必须调用super()
,它会执行基类的构造函数,而且,在构造函数里访问 this
属性之前,一定要调用super() ,这也是TypeScript强制执行的一条重要规则。
公有,私有于受保护的修饰符
默认为 public
在上面的例子里,我们可以自由访问程序里定义的成员,在TypeScript里,成员默认为public
,你也可以明确的将一个成员标记成public
。
class Animal {
public name: string;
public constructor(theName: string){
this.name = theName;
}
public move(distanceInMeters: number){
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
还要注意的是,在构造函数的参数上使用public
等同于创建了同名的成员变量
class Student {
fullName: string;
constructor(public firstName, public middleInitial, public lastName) {
this.fullName = firstName + " " + middleInitial + " " + lastName;
}
}
interface Person {
firstName: string;
lastName: string;
}
function greeter(person : Person) {
return "Hello, " + person.firstName + " " + person.lastName;
}
let user = new Student("Jane", "M.", "User");
document.body.innerHTML = greeter(user);
理解private
当成员被标记成private
时,它就不能在声明它的类的外部访问:
class Animal {
private name: string;
constructor(theName: string){
this.name = theName;
}
}
new Animal('Cat').name; // 错误:’name‘是私有的
理解protected
protected
修饰符与private
修饰符的行为很相似,但有一点不同,protected
成员在派生类(子类)中仍然可以访问:
class Person{
protected name: string;
constructor(name: string){
this.name = name;
}
}
class Employee extends Person{
private department: string;
constructor(name: string, department: string){
super(name)
this.department = department;
}
public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}
let howard = new Employe("Howard", "Sales");
console.log(howard.getElevatorPitch());
console.log(howard.name); // 错误
注意:我们不能在Person
类外使用name
,但是我们仍然可以通过Employee
类的示例方法访问,因为Employee
是由Person
派生而来
构造函数也可以被标记成 protected
。 这意味着这个类不能在包含它的类外被实例化,但是能被继承。比如,
class Person {
protected name: string;
protected constructor(theName: string) { this.name = theName; }
}
// Employee 能够继承 Person
class Employee extends Person {
private department: string;
constructor(name: string, department: string) {
super(name);
this.department = department;
}
public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}
let howard = new Employee("Howard", "Sales");
let john = new Person("John"); // 错误: 'Person' 的构造函数是被保护的.
readonly修饰符
你可以使用 readonly
关键字将属性设置为只读的。 只读属性必须在声明时或构造函数里被初始化。
class Octopus {
readonly name: string;
readonly numberOfLegs: number = 8;
constructor (theName: string) {
this.name = theName;
}
}
let dad = new Octopus("Man with the 8 strong legs");
dad.name = "Man with the 3-piece suit"; // 错误! name 是只读的.
完整的例子:
class TestClass {
constructor(name: string, private address: string, public city){
testMethod(){
console.log(this.name)// Compiler error: property 'name' does not exist on type 'TestClass'.
console.log(this.address);
console.log(this.city);
}
}
}
const testClass = new TestClass('Johnson', '8 gaoke road', 'NanNing');
testClass.testMethod();
console.log(testClass.name); // Compiler error: property 'name' does not exist on type 'TestClass'.
console.log(testClass.address); // Compiler error: 'address' is private and only accessible within class 'TestClass'.
console.log(testClass.city) // NanNing
TypeScript 参数属性
先看例子:
class Octopus {
// 只读属性必须在声明时或者构造函数里被初始化
readonly name: string;
readonly numberOfLegs: number = 8;
constructor (theName: string) {
this.name = theName;
}
}
let dad = new Octopus("Man whti the 8 strong legs");
dad.name = "Man with the 3-piece suit"; // 错误!name是只读的
Octopus
定义了一个只读成员name
和一个参数为theName
的构造函数,并且立刻将theName
的值赋给name
,这种情况经常会遇到。参数属性可以方便的让我们在一个地方定义并初始化一个成员,重构如下:
class Octopus {
readonly numberOfLegs: number = 8;
constructor(readonly name: string){
}
}
我们舍弃了theName
,仅在构造函数中使用readonly name: string
参数来创建和初始化name
成员,把声明和赋值并至一处。
参数属性通过给构造函数参数前面添加一个访问限定符来声明使用private
限定一个参数属性会声明并初始化一个私有成员;对于public
和protected
来说也是一样。
类的优势
- 创建的函数编写的代码变少了
- 在类里面,可以清晰的指定构造函数
- 类需要的所有代码都包含在类声明中,可以设定一切内容,不需要向原型一个一个去添加方法
注意
class
只是语法糖class
是原型继承的抽象形式- 在创建类的新实例,必须使用关键字
new
总结:
Angular 有些constructor是简写的方式,并没有主体内容,理解背后的原理至关重要。