Class
可以通过
extends
关键字实现继承,这比 ES5 的方法要方便很多。
在ES5中实现继承,分两步:
function Sub(value) {
Super.call(this);
this.prop = value;
}
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
Sub.prototype.method = '...';
下面是一个具体的例子:
// 父类
function A(x, y) {
this.x = x;
this.y = y;
}
// 父类原型添加方法
A.prototype.move = function(x, y) {
this.x = x;
this.y = y;
}
// 子类
function B(x, y, z) {
// 1.调用父类构造函数
A.call(this, x, y);
this.z = z;
}
// 2. 子类原型指向父类原型
B.prototype = Object.create(A.prototype);
B.prototype = B;
// 创建子类实例
let b = new B(0, 0, 0);
b instanceof A; // true
b instanceof B; // true
// 调用父类原型上的方法move
b.move(1, 2);
上面例子中,instanceof
运算符会对子类和父类的构造函数,都返回true
。
上面写法是子类整体继承父类,有时候只需要继承单个方法,可以使用下面的写法:
Sub.prototype.methodName = function(value) {
// 调用父类方法
Super.prototype.methodName.call(this);
// 实现自己功能
....
}
JavaScript 不提供多重继承功能,即不允许一个对象同时继承多个对象。但是,可以通过Mixin(混入),实现这个功能。
// 父类A
function A() {
this.x = "A";
}
// 父类B
function B(){
this.y = "B"
}
// 子类C
function C() {
// 1.调用父类构造
A.call(this);
B.call(this);
this. y = "C";
}
// 2. 子类C继承父类A原型
C.prototype = Object.create(A.prototype);
// 3. 子类原型加入父类B原型
Object.assign(C.prototype, B.prototype);
C.prototype.constructor = C;
Class 可以通过extends
关键字实现继承。
// 父类
class Super{
...
}
class Sub extends Super {
...
}
下面是一个具体例子:
// 父类
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
toString() {
return "姓名:" + this.name + " 年龄:" + this.age;
}
}
// 子类
class Student extends Person {
constructor(name, age, score) {
// 调用父类构造
super(name, age);
this.score = score;
}
toString() {
return super.toString() + " 成绩:" + this.score;
}
}
let rycony = new Student("rycony", 24, 89.0);
rycony.toString(); // "姓名:rycony 年龄:24 成绩:89"
上面代码中,在子类构造函数constructor
通过super()
调用父类构造方法,在子类原型方法toString
通过super.toString()
调用父类原型上的toString
方法。
ES5 的继承实质上是:先创造子类的实例对象this
,然后再将父类的方法添加到this
上面(Super.call(this)
)。
ES6 的继承实质上是:先将父类实例对象的属性和方法,加到this
上面,然后再用子类的构造函数修改this
。
如果子类没有定义constructor
方法,这个方法会被默认添加。
// 父类A
class A {}
// 子类B继承父类A
class B extends A {}
// 相当于
class B extends A {
constructor(...args) {
super(...args);
}
}
super
之后,才可以使用
this
关键字,否则会报错。因为**子类实例的构建,基于父类实例**。
// 父类A
class A {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
// 子类(先使用this)
class B extends A {
constructor(x, y, z) {
this.z = z;
super(x, y);
}
}
new B(1, 2, 3); // ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
// 子类(先使用super调用父类构造,再使用this)
class C extends A {
constructor(x, y, z) {
super(x, y);
this.z = z;
}
}
new C(1, 2, 3); // C {x: 1, y: 2, z: 3}
上面例子中,类B
先使用this
,会报错。
子类通过extends
继承父类,连父类的静态方法也会被继承。
class M {
static print() {
console.info("你好,世界!");
}
}
class N extends M {
}
N.print(); // 你好,世界!
上面例子中,子类N
继承了父类M
,父类M
的静态方法print
也会被子类N
继承,通过N.print()
也能调用父类的静态方法print()
。
Object.getPrototypeOf
方法可以用来从子类上获取父类。
class A {}
class B extends A{}
Object.getPropertyOf(B) === A; // true
super
关键字,既可以当作函数使用,也可以当作对象使用。
super
作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super
函数。
class A {}
class B extends A{
constructor() {
this.x = "jidi";
}
}
new B(); // ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
上面例子中,子类B
继承父类A
,但是在子类B
构造函数中没有通过super
调用父类构造函数,会报错。
super
虽然代表了父类的构造函数,但是返回的是子类的实例,即super
内部的this
指的是子类的实例,因此super()
相当于Super.prototype.constructor.call(this)
。
class A {
constructor() {
console.info(new.target.name);
}
}
class B extends A {
constructor() {
super();
}
}
new A(); // A
new B(); // B
上面例子中,new.target.name
指向当前正在执行的函数,在子类B
的构造函数通过super()
调用父类构造函数,指向的是子类B
的构造函数,即super()
内部的this
指向子类实例。
super
作为函数调用,只能用在子类构造函数中,否则会报错。
class A {}
class B extends A {
print() {
super(); // SyntaxError: 'super' keyword unexpected here
}
}
super
作为对象调用,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
class A {
constructor(x, y) {
this.x = x;
this.y = y;
}
m() {
return "jidi";
}
static n() {
return 23;
}
}
class B extends A{
constructor(x, y) {
super(x, y);
}
mm() {
return super.m(); // super指向父类原型对象
}
static nn() {
return super.n(); // super指向父类
}
}
let x = new B(1,2);
x.mm(); // "jidi"
B.nn(); // 23
上面例子中,在子类B
方法mm
中,通过super.m()
调用父类A
的方法m
,此时super
指向父类原型对象;在子类静态方法nn
中,通过super.n()
调用父类静态方法n
,此时,super
执行父类。(上面例子中,子类的两个方法其实都没有意义,因为子类继承父类,会继承父类原型上的方法和父类静态方法,这里只是作为例子使用)。
在普通方法中,super
指向父类的原型对象,定义在父类实例上的方法或属性,是无法通过super
调用的。
class A {
x = "jidi";
constructor(value) {
this.y = value;
}
}
class B extends A {
constructor(value) {
super(value);
}
print() {
console.info(super.x);
}
}
new B(23).print(); // undefined
上面例子中,子类B
继承父类A
,在方法print
中通过super.x
想调用父类A
的实例属性x
,结果为undefined
。
如果属性定义在父类原型上,super
可以获取到属性。
class A { }
A.prototype.x = "jidi";
class B extends A{
y() {
return super.x; // 调用父类原型对象属性x
}
}
new B().y(); // "jidi"
上面例子中,通过super.x
就能调用到属性x
,因为x
定义在父类的原型对象上面。
在子类普通方法中通过super
调用父类的方法时,方法内部的this
指向当前的子类实例。
class A {
constructor() {
this.name = "jidi";
}
print(){
console.info(this.name);
}
}
class B extends A{
name = "rycony";
m() {
super.print(); // super调用父类方法,方法内部this指向当前子类实例
}
}
let b = new B();
b.m(); // "rycony"
上面例子中,子类B
方法m()
中通过super.print()
调用了父类A
的print()
方法,此时print()
方法内部的this
指向当前子类实例。
通过super
对某个属性赋值,赋值的属性会变成子类实例的属性。
class A{
constructor(){
this.name = "jidi";
}
}
class G extends A{
constructor(){
super();
this.age = 21;
super.age = 23; // 通过super给属性赋值
}
getAge(){
super.age = 33;
console.info(super.age)
return this.age;
}
}
x = new G(); // {name: "jidi", age: 23}
x.getAge(); // undefined 33
上面例子中,子类G
在构造方法和普通方法getAge
中通过super.age
为属性age
赋值,赋值的属性age
会成为子类实例的属性,相当于调用this.age
赋值。但是,当通过super.age
读取属性时,读取的是父类原型对象的属性,相当于A.prototype.age
,所以为undefined
。
在子类的静态方法中通过super
调用父类的方法时,方法内部的this
指向当前的子类。
class A {
constructor() {
this.x = 1;
}
static print() {
console.log(this.x);
}
}
class B extends A {
constructor() {
super();
this.x = 2;
}
static m() {
super.print();
}
}
B.x = 3;
B.m() // 3
上面例子中,静态方法B.m
里面,super.print
指向父类A
的静态方法。这个方法里面的this
指向的是子类B
,不是子类B
的实例。
使用super
关键字,必须显式指定是作为函数、还是作为对象调用,否则会报错。
class A {}
class B extends A{
m(){
console.log(super); // SyntaxError: 'super' keyword unexpected here
}
}
ES6中的原生构造函数有:
Boolean()
Number()
String()
Array()
Date()
Function()
RegExp()
Error()
Object()
ES6 允许继承原生构造函数自定义子类。
class MyFirstArray extends Array {
constructor(...args){
super(...args);
}
getName() {
return "jidi";
}
}
let x = new MyFirstArray();
x[0] = 1;
x[1] = 2;
x.length; // 2
x.getName(); // "jidi"