ES6引入class语法之后,JavaScript的面向对象编程终于有了更接近传统语言的写法。在class继承体系中,super关键字扮演着承上启下的关键角色:它既可以作为函数调用父类的构造函数,也可以作为对象访问父类的属性和方法。很多开发者虽然每天都在用extends和super,但对它的底层行为一知半解,遇到“Must call super constructor before accessing this”这类报错时往往一脸茫然。本文将系统地讲解super在class继承中的各种用法和原理。

super作为函数:调用父类构造函数
当子类通过extends继承父类后,如果父类的构造函数需要初始化参数,子类的constructor里就必须显式调用super()。这是super最常见的第一种用法——作为函数直接调用。它的本质是执行父类的constructor函数,完成父类部分的初始化工作。
先看一个基础示例:
class Animal {
constructor(name) {
this.name = name;
}
eat() {
console.log(this.name + ' is eating');
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // 调用父类构造函数,初始化 this.name
this.breed = breed; // 必须在 super 之后才能使用 this
}
}
const dog = new Dog('旺财', '柴犬');
dog.eat(); // 旺财 is eating</code>这里有一个非常重要的规则:子类构造函数中,this必须在super()调用之后才能使用。这不是语法上的随意约定,而是由class继承的底层机制决定的。ES6的继承通过先创建父类的实例对象this,然后再用子类的构造函数去修改它来实现。也就是说,子类的this是“借用”父类构造函数构建出来的。在super()执行之前,this根本还不存在,任何对this的访问都会抛出ReferenceError。
class Dog extends Animal {
constructor(name, breed) {
this.breed = breed; // 报错!this 还未初始化
super(name);
}
}
// Uncaught ReferenceError: Must call super constructor
// before accessing 'this'</code>还有一个容易忽略的细节:如果子类没有定义constructor,JavaScript会自动生成一个默认构造函数,内容相当于constructor(...args) { super(...args); },即把所有参数透传给父类。所以子类不需要额外初始化逻辑时,可以完全不写constructor。
super作为对象:访问父类的原型方法和静态方法
super的第二种用法是在普通方法(原型方法)中作为对象使用。此时super.method()指向的是父类的原型对象上的方法,但方法执行时的this却绑定在当前子类实例上。这一点是理解super行为的关键。
看下面的代码:
class Animal {
constructor(name) {
this.name = name;
}
eat() {
console.log(this.name + ' is eating');
}
}
class Dog extends Animal {
eat() {
super.eat(); // 调用父类的 eat,但 this 指向 Dog 实例
console.log(this.name + ' 在摇尾巴');
}
}
const dog = new Dog('旺财');
dog.eat();
// 输出:
// 旺财 is eating
// 旺财 在摇尾巴</code>super.eat()等价于Animal.prototype.eat.call(this)。引擎在解析super时,会沿着当前对象的原型链向上查找,找到定义该方法的父类原型。正因如此,在子类方法中通过super调用父类方法时,父类方法内部的this指向子类实例,所有属性读写都发生在子类实例上。
在静态方法中,super的指向有所不同:super.staticMethod()指向的是父类本身而不是父类原型。这样子类静态方法内可以通过super复用父类的静态逻辑:
class Animal {
static create(name) {
return new this(name); // this 指向调用者,即 Dog
}
}
class Dog extends Animal {
static create(name) {
const dog = super.create(name); // 调用 Animal.create
dog.fromFactory = true;
return dog;
}
}
const d = Dog.create('旺财');
console.log(d.fromFactory); // true</code>需要注意的是,通过super对属性赋值时,super.x = 1实际等价于this.x = 1,赋值动作落在当前对象上;而读取super.x才是沿原型链查找父类的属性。读写行为的不对称性也是super的一个隐蔽知识点。
super的绑定特性与常见坑
super的指向不是动态的,而是在方法定义时就固定绑定到声明它所在的那个对象环境。JavaScript内部用一个内部槽来记录super的绑定目标,这意味着即使把方法取出来赋给其他对象再调用,super依然指向原来父类的原型。
class Animal {
eat() {
console.log('animal eat');
}
}
class Dog extends Animal {
eat() {
super.eat();
}
}
const obj = { eat: Dog.prototype.eat };
// obj.eat(); // 报错,super 绑定已经丢失或指向错误</code>箭头函数是这里的一个特例。箭头函数没有自己的this和super绑定,它会捕获外层函数的绑定。所以在class方法内部使用箭头函数访问super是完全合法的,super指向与外层方法一致:
class Dog extends Animal {
eat() {
const callParent = () => super.eat(); // 捕获外层的 super 绑定
setTimeout(callParent, 100); // 异步调用也不受影响
}
}</code>此外还要注意,super只能在子类的方法或构造函数中使用,脱离class环境直接调用super会抛出语法错误。而且super不能单独当变量读取,必须以super.prop或super.method()的形式出现(构造函数中的super()除外)。
extends的两种特殊继承场景
除了普通class之间的继承,extends还有两个值得掌握的特殊用法。第一个是继承内置对象(原生类),比如继承Array、Error等。ES6的class继承可以正确获取原生对象的内部特性,这是旧式寄生组合继承做不到的:
class MyArray extends Array {
getLast() {
return this[this.length - 1];
}
}
const arr = new MyArray(1, 2, 3);
console.log(arr.getLast()); // 3
console.log(arr instanceof Array); // true</code>第二个是extends null,这种写法创建的类没有原型链上的父对象,类似一个“纯净类”。但要小心的是,这种类的默认constructor依然会尝试调用super(),而null没有构造函数,会直接报错,所以必须显式定义constructor并手动返回一个对象:
class Pure extends null {
constructor() {
// 不能调用 super(),需要手动返回对象或借助 Object.create
return Object.create(Pure.prototype);
}
}
const p = new Pure();
console.log(Object.getPrototypeOf(p) === Pure.prototype); // true</code>掌握这些细节后,再回头看super的设计就非常清晰了:作为函数,它负责构建父类部分的this;作为对象,它提供了在子类中安全复用父类逻辑的通道。理解super的绑定规则和this指向,是写出可靠的class继承代码、读懂各类框架源码中继承实现的基础。
JS class继承super关键字JavaScript面向对象修改时间:2026-09-09 23:58:40