ES6 Class 继承机制详解:super、原型链与 Vue 实战

作者:纸飞机

时间:2026-10-23

浏览:377

本文深入解析 ES6 中 Class 的继承机制,重点讲解 extends 关键字与 super 函数的用法。详细阐述子类构造函数中必须调用 super 的原因、this 指向规则,以及 super 作为函数和对象时的区别。同时分析 prototype 与 proto 两条继承链,并结合 Vue 实例展示如何在实际开发中应用 Class 继承。

文章目录

本文通过具体实例解析 ES6 中 Class 的继承用法,供读者参考学习。

JavaScript 是一种基于对象的语言,其面向对象实现方式与传统面向对象语言存在显著差异。ES6 引入了 Class 语法糖,使 JavaScript 的继承写法更接近传统面向对象语言的风格。

本文内容主要分为两部分:基础概念介绍与 Vue 框架下的使用案例。

基础介绍

Class 可以通过 extends 关键字实现继承。相较于 ES5 中通过修改原型链来实现继承的方式,这种方式更加清晰且便捷。


class Father {
 }
class Son extends Father {
}

代码定义了一个 Son 类,该类通过 extends 关键字继承了 Father 类的所有属性和方法。由于 Son 类中未部署任何具体代码,因此这两个类完全一致,相当于直接复制了 Father 类。


class Son extends Father {
     constructor (name,age,city) {
        super(name,age);//调用父类的constructor(name,age);
        this.city = city;
      }
 
      toString () { 
         return this.city+ " " +super.toString();//调用父类的toString()
      }
}

在 constructor 方法和 toString 方法中均出现了 super 关键字。此处的 super 代表父类的构造函数,用于创建父类的 this 对象。

子类必须在 constructor 方法中调用 super 方法,否则创建实例时会报错。这是因为子类自身没有 this 对象,而是继承自父类的 this 对象并进行加工。若不调用 super 方法,子类将无法获得 this 对象。


class Father {  }
 
class Son extends Father {
     constructor(){ }
}
let s = new Son();
//referenceError : this is not defined 

Son 类继承了父类 Father,但其构造函数未调用 super 方法,导致创建实例时报错。ES5 的继承机制实质是先创造子类的实例对象 this,再将父类的方法添加到 this 上(通过 Parent.apply(this))。而 ES6 的继承机制完全不同,其实质是先创造父类的实例对象 this(因此必须先调用 super 方法),然后再用子类的构造函数修改 this。如果子类未定义 constructor 方法,系统会默认添加该方法。这意味着无论是否显式定义,任何子类都拥有 constructor 方法。


class Son extends Father {
}
 
//等同于
class Son extends Parent {
    constructor(...args) {
    super(...args);
   }
}

另一个关键点:在子类的构造函数中,只有在调用 super 之后,才能使用 this 关键字,否则将报错。这是因为子类实例的构建基于对父类实例的加工,只有 super 方法能返回父类实例。


class Father {
   constructor (x,y) {
      this.x= x;
      this.y = y;
    }
}
 
class Son extends Father {
   constructor (x, y, color) {
       this.color =color ;//ReferenceError : this is not defined
      super(x,y);
       this.color = color;//正确
      }
}
 
let s = new Son(25,8,"green");
s instanceof Son //true 
s instanceof Father //true

若在子类的 constructor 方法中未调用 super 之前就使用 this 关键字,结果会报错;而将 this 的使用放在 super 方法之后则是正确的做法。

Object.getPrototypeOf() 方法用于从子类获取父类。


Object.getPrototypeOf( Son ) ===Father
//true
//因此可以用这个方法判断,一个类是否继承了另一类

super 关键字既可以作为函数使用,也可以作为对象使用。第一种情况是当 super 作为函数调用时,它代表父类的构造函数。ES6 规定,子类的构造函数必须执行一个 super 函数。


class Father { }
 
class Son extends Father {
    constructor () {
          super();
       }
}
//子类Son的构造函数之中的super(),代表调用父类的构造函数。这是必须的,否则 JavaScript 引擎会报错。

虽然 super 代表了父类 Father 的构造函数,但其返回的是子类 Son 的实例。即 super 内部的 this 指向的是 Son,因此 super() 在此处相当于 Father.constructor.call(this)。此外,作为函数时,super() 只能用在子类的构造函数中,在其他位置调用会报错。


class A {
     constructor (){
        console.log(new.target.name);
      }
 }
 
class B extends A {
   constructor () {
      super();
      }
 }
  new A()//A
 new B()//B 

new.target 指向当前正在执行的函数。在 super() 执行时,它指向的是子类 B 的构造函数,而非父类 A 的构造函数,且 super() 内部的 this 指向的是 B。

第二种情况是当 super 作为对象使用时:在普通方法中,它指向父类的原型对象;在静态方法中,它指向父类本身。


class Father{
   getName ( ) {
     return "MGT360124";
   }
}
class Son extends Father {
    constructor () {
    super();
    console.log(super.getName() ) //“MGT360124”
    }
}
let s = new Son();

子类 Son 中的 super.p() 是将 super 当作对象使用。此时,在普通方法中,super 指向 Father.prototype,因此 super.getName() 相当于 Father.prototype.getName(),输出结果为 "MGT360124"。由于 super 指向父类的原型对象,因此定义在父类实例上的方法或属性无法通过 super 调用。


class Father {
   constructor () {
       this.p =2
     }
}
 
class Son extends Father {
     get m ( ) {
          return super.p;
     }
     getValue ( ) {
          return super.a;
      }
}
let s = new Son();
s.m
//undefined

p 是父类 Father 实例的属性,通过 super.p 无法引用到它。

如果属性定义在父类的原型对象上,super 就可以获取到该属性。


class A {}
A.prototype.x = 2;
 
class B extends A {
 constructor() {
  super();
  console.log(super.x) // 2
 }
}
 
let b = new B();

属性 x 定义在 A.prototype 上,因此 super.x 可以获取到它的值。

ES6 规定,通过 super 调用父类的方法时,super 会绑定子类的 this。


class Father {
   constructor () {
      this.x =1;//这个this指向的是Father对象的实例
   }
   print () {
      console.log(this.x);
   }
}
 
class Son extends Father {
   constructor () {
       super();
        this.x = 2;//这个this指向的是Son对象的实例
   }
     m() {
      super.print();    
     }
}
let s = new Son();
s.m();
//2 

super.print() 虽然调用的是 Father.prototype.print(),但 Father.prototype.print() 会绑定子类 Son 的 this,导致输出结果为 2 而非 1。也就是说,实际上执行的是 super.print.call(this)。

如果 super 作为对象用在静态方法中,此时 super 将指向父类,而非父类的原型对象。


class Parent {
      static myMethod (msg) {
           console.log("static",msg);
        }
      myMethod (msg) {
          console.log("instance" ,msg);
        }
}
 
class Child extends Parent {
     static myMethod(msg) {
        super.myMethod(msg);
     }
      myMethod (msg) {
      super.myMethod(msg);
      }
 }
 
Child.myMethod(1);
//static 1
var child = new Child();
child.myMethod(2);
//instance 2

super 在静态方法中指向父类,在普通方法中指向父类的原型对象。注意,使用 super 时必须显式指定是作为函数还是作为对象使用,否则会报错。关于类的 prototype 属性和 proto 属性:大多数浏览器的 ES5 实现中,每个对象都有 proto 属性,指向对应的构造函数的 prototype 属性。Class 作为构造函数的语法糖,同时拥有 prototype 属性和 proto 属性,因此存在两条继承链:(1)子类的 proto 属性表示构造函数的继承,总是指向父类;(2)子类 prototype 属性的 proto 属性表示方法的继承,总是指向父类的 prototype 属性。


class A{
}
class B{
}
//B的实例继承A的实例
Object.setPrototypeOf(B.prototype, A.prototype);
 
//B 的实例继承A的静态属性
Object.setPrototypeOf(B,A);
 
const b = new B();

《对象的扩展》一章中 Object.setPrototypeOf() 方法的实现代码如下。


Object.setPrototypeOf = function (obj, proto) {
obj.__proto__ = proto;
  return obj ;
}

因此,


Object.setPrototypeOf( B.prototype , A.prototype );
//等同于
B.prototype.__proto__ = A.prototype ;
 
Object.setPrototypeOf(B, A);
//等同于
B.__proto__ = A;

这两条继承链可以理解为:作为一个对象,子类 B 的原型(proto 属性)是父类 A;作为一个构造函数,子类 B 的原型对象(prototype 属性)是父类的原型对象(prototype)的实例。

extends 的继承目标


class B extends A{
}

extends 关键字后面可以跟随多种类型的值。只要 A 是具有 prototype 属性的函数,就能被 B 继承。由于函数都有 prototype 属性(除了 Function.prototype 函数),因此 A 可以是任意函数,包含以下三种情况:(1)子类继承 Object 类。


class A extends Object {
}
A.__proto__ === Object //true;
A.prototype.__proto__ === Object.prototype //true

这种情况即 A 是构造函数 Object 的复制,A 的实例即为 Object 的实例。(2)不存在任何继承。


class A {
}
A.__proto__ === Function.prototype //true
A.prototype.__proto__ = Object.prototype //true

这种情况中,A 作为一个基类(不存在任何继承),是一个普通函数,因此直接继承 Function.prototype。但 A 调用后返回一个空对象(即 Object 实例),所以 A.prototype.proto 指向构造函数(Object)的 prototype 属性。关于子类实例的 proto 属性:子类实例的 proto 属性的 proto 属性,指向父类实例的 proto 属性。也就是说,子类的原型的原型,是父类的原型。

原生构造函数的继承


Boolean()
Number()
String()
Array()
Date()
Function()
RegExp()
Error()
Object()

原生构造函数是指语言内置的构造函数,通常用于生成数据结构。extends 关键字不仅可以用来继承类,还可以用来继承原生的构造函数。因此,可以在原生数据结构的基础上,定义自己的数据结构。

Vue 使用

testClass.js


//定义类
class Person{ 
	// 构造 
	constructor(x,y){ 
		this.x = x; 
		this.y = y; 
	} 
 
  //定义在类中的方法不需要添加function
	toString(){ 
		return (this.x + "的年龄是" +this.y+"岁"); 
	} 
} 
export {
	Person
}; 

test.vue


<template>
	<div>
		<p id="testJs"></p>
	</div>
</template>
<script>
import {Person} from './testClass.js'; 
export default {  
	data() {
		return {
		}
	},
	mounted(){
		let text=document.getElementById("testJs");
		//使用new的方式得到一个实例对象
		let person = new Person('张三',12); 
		text.innerHTML=person.toString();//张三的年龄是12岁
		console.log(typeof Person);//function 
	}
}
</script>

感兴趣的朋友可以使用在线 HTML/CSS/JavaScript 代码运行工具:http://tools.jb51.net/code/HtmlJsRun 测试上述代码的运行效果。

更多关于 JavaScript 相关内容感兴趣的读者可查看本站专题:《javascript 面向对象入门教程》、《JavaScript 错误与调试技巧总结》、《JavaScript 数据结构与算法技巧总结》、《JavaScript 遍历算法与技巧总结》及《JavaScript 数学运算用法总结》。

希望本文所述对大家 JavaScript 程序设计有所帮助。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章