JavaScript 面向对象 (三)

119 阅读3分钟

前言

在 ECMAScript 6 中新引入了 class 关键字,具有定义类的能力。其实际上还是使用了原型和构造函数的概念。

类的类型时 function ,类不进行声明提升,类受块级作用域的限制,类可以作为参数传递

class Person {
  constructor(name) {
    this._myName = name;
  }

  sayName() {
    console.log('我的名字是: ' + this._myName);
  }

  set myName(value) {
    this._myName = value;
  }

  get myName() {
    return this._myName;
  }

  static sayHi() {
    console.log('Hi');
  }
}

var a = new Person('knight');
a;

class Son extends Person {
  constructor(name, age) {
    super(name);
    this.age = age;
  }
}

image1.png

使用类有以下好处:

  • 不再引用杂乱的 .prototype
  • Button 声明时直接“继承”了 Widget ,不再需要通过 Object.create() 来替换 .ptototype 对象,也不需要设置 .__proto__ 或者 Object.setPrototypeOf()
  • 可以通过 super() 来实现相对多态,这样任何方法都可以引用原型链上层的同名方法。
  • class 字面语法不能声明属性,只能声明方法。会排除掉许多不好的情况
  • 可以通过 extends 很自然地扩展对象子类型,甚至时内置的对象子类型

类的声明

我们可以通过类声明和类表达式来声明一个类:

// 类声明
class Person1 {}
// 类表达式
var Person2 = class {}

类构造函数

如果我们声明类的时候不定义类构造函数,则相当于将构造函数定义为空函数,如果我们的类有父类,则自动调用父类的构造函数。

使用 new 操作符实例化 Person 的操作等于使用 new 调用其构造函数

使用 new 调用类的构造函数会执行如下操作:

  • 在内存中创建一个新对象
  • 这个新对象内部的 [[Prototype]] 指针(存储其原型对象,对开发者不可见,在大多数浏览器中可以通过 __proto__ 去访问)被赋值为构造函数的 prototype 属性
  • 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象
  • 执行构造函数内部的代码(给新对象添加属性)
  • 如果构造函数发挥非空对象,则返回该对象;否则返回刚创建的新对象

在实例化之后,类构造函数会以普通函数作为实例的一个属性

image2.png

方法

我们可以添加方法和静态类方法,也可以添加获取和设置访问器。

我们添加的方法将作为原型方法定义在原型对象中,供所有实例调用

静态方法将定义在 .contructor 构造函数中,可通过类名调用

设置的访问器将作为实例的属性,添加到实例对象中

class Person {
  constructor(name) {
    this._myName = name;
  }

  sayName() {
    console.log('我的名字是: ' + this._myName);
  }

  set myName(value) {
    this._myName = value;
  }

  get myName() {
    return this._myName;
  }

  static sayHi() {
    console.log('Hi');
  }
}

var a = new Person('knight');
a;

image3.png

类的继承

继承类可通过 extends 关键字,不但可以继承类,还可以继承构造函数

function First(name) {
  this.name = name;
}
First.name = 'first';

class Second extends First {
  constructor(name, age) {
    super(name);
    this.age = age
  }
}
Second.name = 'second';

class Third extends Second {}
Third.name = 'third';

var a = new First('knight');
var b = new Second('knight', 19);
var c = new Third('knight', 18);

image4.png

super

派生类的方法可以通过 super 关键字引用他们的原型。

使用 super 有一下几个注意点:

  • 这个关键字只能在派生类中使用,而且仅限于类构造函数、实例方法和静态方法内部
  • 不能单独引用 super 关键字
  • 调用 super() 会调用父类构造方法,并将返回的实例赋值给 this
  • super() 的行为如同调用构造函数,如果需要给父类构造函数传参,需要手动传入
  • 如果没有定义类构造函数,在实例化派生类时会调用 super() ,而且会传入所有传给派生类的参数
  • 在类构造函数中,不能在调用 super() 之前引用 this
  • 如果在派生类中显示定义了构造函数,则要么必须在其中国调用 super() ,要么必须在其中返回一个对象
class Person {
  constructor(name) {
    this._myName = name;
  }

  sayName() {
    console.log('我的名字是: ' + this._myName);
  }

  set myName(value) {
    this._myName = value;
  }

  get myName() {
    return this._myName;
  }

  static sayHi() {
    console.log('Hi');
  }
}

class Son extends Person {
  constructor(name, age) {
    super(name);
    this.age = age;
  }

  sayInfo() {
    super.sayName();
    console.log('我的年龄是: ' + this.age);
  }

  static sayHellow() {
    super.sayHi();
  }
}

var a = new Son('knight', 18);

image5.png