ES5中,社区通过各种方式来实现类似于类(class-like)的行为。但各种方式都有自己的问题和妥协。所以实现继承的代码也显得非常冗长和混乱
原型链继承:不能复用方法,引用类型会被共享,子类型在实例化时不能给父类型的构造函数传参。
盗用构造函数继承:解决原型包含引用值导致共享问题,以及可以向父类构造函数传递参数的问题。但还有方法不能复用的问题。
组合继承:组合继承弥补了原型链和盗用构造函数的不足,是 JavaScript 中使用最多的继承模式。但却有效率问题。即父类够造函数会执行两次。
寄生式组合继承:解决了组合继承重复调用两次父类构造函数,以及子类原型上不必要的重复属性。寄生式组合继承可以算是引用类型继承的最佳模式。
为解决这些问题,ECMAScript 6 新引入的 class 关键字具有正式定义类的能力。class 表面上看起来像是支持正式的面向对象编程,但实际上还是基于原型和够造函数实现的。

类构造函数

当我们调用new时,调用的就是constructor。构造函数的定义不是必需的,不定义构造函 数相当于将构造函数定义为空函数。
类构造函数与普通够造函数创建对象的过程是一样的,主要区别是,调用类构造函数必须使用 new 操作符。而普通构造函数如果 不使用 new 调用,那么就会以全局的 this(通常是 window)作为内部对象。调用类构造函数时如果忘了使用 new 则会抛出错误:

  1. function Person() {}
  2. class Animal {}
  3. // 把 window 作为 this 来构建实例
  4. let p = Person();
  5. let a = Animal();
  6. // TypeError: class constructor Animal cannot be invoked without 'new'

另外的一个区别就是 函数申明会提升,而类声明是不会提升的。

类是一个特殊函数

ECMAScript 中没有正式的类这个类型,,ECMAScript 类就是一种特殊函数。声明一个类之后,通过 typeof 操作符检测类标识符,表明它是一个函数:

  1. class Person {}
  2. console.log(Person); // class Person {}
  3. console.log(typeof Person); // function 是一个函数

类本身具有与普通够造函数一样的行为

  1. //class
  2. class Person{}
  3. console.log(Person.prototype); // { constructor: f() }
  4. console.log(Person === Person.prototype.constructor); // true
  5. let p = new Person();
  6. console.log(p instanceof Person); // true
  7. //普通构造函数
  8. function Student(){}
  9. console.log(Student.prototype); // { constructor: f() }
  10. console.log(Student === Student.prototype.constructor); // true
  11. let student = new Student()
  12. console.log(student instanceof Person);//true

实例成员、原型成员和类成员

类的语法可以非常方便地定义应该存在于实例上的成员、应该存在于原型上的成员,以及应该存在于类本身的成员。

实例成员

new调用类标识符时,会调用constructor函数,这个constructor函数返回的对象就是每个实例对象,所以在constructor中定义的属性和方法都是实例成员

  1. class Person {
  2. constructor() {
  3. // 使用包装对象为的是在下面测试两个对象的相等性
  4. this.name = new String('Jack');
  5. this.sayName = () => console.log(this.name);
  6. this.nicknames = ['Jake', 'J-Dog']
  7. }
  8. }
  9. let p1 = new Person(), p2 = new Person();
  10. p1.sayName(); // Jack
  11. p2.sayName(); // Jack
  12. console.log(p1.name === p2.name); // false
  13. console.log(p1.sayName === p2.sayName); // false
  14. console.log(p1.nicknames === p2.nicknames); // false

原型成员

为了在实例间共享方法,类定义语法把在类块中定义的方法作为原型方法。

  1. class Person {
  2. constructor() {
  3. // 添加到 this 的所有内容都会存在于不同的实例上
  4. this.locate = () => console.log('实例成员');
  5. }
  6. // 在类块中定义的所有内容都会定义在类的原型上
  7. locate() {
  8. console.log('原型成员');
  9. }
  10. }
  11. let p = new Person();
  12. p.locate(); // 实例成员
  13. Person.prototype.locate(); // 原型成员

类成员

可以在类上定义静态方法。这些方法通常用于执行不特定于实例的操作,也不要求存在类的实例。静态类成员在类定义中使用 static 关键字作为前缀。在静态成员中,this 引用类自身。其他所 有约定跟原型成员一样:

  1. class Person {
  2. constructor() {
  3. // 添加到 this 的所有内容都会存在于不同的实例上
  4. this.locate = () => console.log('instance', this);
  5. }
  6. // 定义在类的原型对象上
  7. locate() {
  8. console.log('prototype', this);
  9. }
  10. // 定义在类本身上
  11. static locate() {
  12. console.log('class', this);
  13. }
  14. }
  15. let p = new Person();
  16. p.locate(); // instance, Person {}
  17. Person.prototype.locate(); // prototype, {constructor: ... }
  18. Person.locate(); // class, class Person {...}

数据成员

类定义并不显式支持在原型或类上添加成员数据,但在类定义外部,可以手动添加:

class Person { 
 sayName() { 
 console.log(`${Person.greeting} ${this.name}`); 
 } 
} 
// 在类上定义数据成员
Person.greeting = 'My name is';
// 在原型上定义数据成员
Person.prototype.name = 'Jake'; 
let p = new Person(); 
p.sayName(); // My name is Jake

ES6的继承

ES6 类支持单继承。使用class定义的类 可以继承任何拥有[[Construct]]和原型的对象。意味着,class可以继承一个普通的构造函数(保持向后后兼容)

class Vehicle {} 
// 继承类
class Bus extends Vehicle {}

function Person() {} 
// 继承普通构造函数
class Engineer extends Person {}
const chentao = new Engineer()
console.log(e instanceof Engineer); // true 
console.log(e instanceof Person); // true

派生类都会通过原型链访问到类和原型上定义的方法:

class Vehicle { 
 //原型上的方法
 identifyPrototype(id) { 
 console.log(id, this); 
 } 
 //类上的方法
 static identifyClass(id) { 
 console.log(id, this); 
 } 
} 
class Bus extends Vehicle {}
const yutongkeche = new Bus()
const benchi = new Vehicle()

//通过实例调用的方法是原型上的方法
yutongkeche.identifyPrototype()// Bus {} 
benchi.identifyPrototype()// Vehicle {} 

//通过类调用的方法是类上的方法(静态方法)
Vehicle.identifyClass()//class Vehicle {...}
Bus.identifyClass()//class Bus extends Vehicle {}

super方法