导读:本期聚焦于小何创作的《如何正确将 ES6 类转换为传统函数构造器并保持原型链继承》,敬请观看详情。把ES6类转成传统函数构造器时,不少人会直接把所有方法都塞到构造函数里,结果导致每个实例都重复创建方法副本,既浪费内存又破坏了原型链继承的逻辑。实际上ES6类的底层实现就是基于传统构造器和原型链的语法糖,转换时需要严格对应类的构造函数、原型方法、静态属性这几个核心部分。只要理清类定义和构造器、原型对象、静态属性的对应关系,就能在转换后完全保留原有的继承逻辑和实例行为,不会出现方法丢失或者原型指向错误的问题。

ES6引入的类语法让JavaScript的面向对象写法更清晰,但本质上它只是传统基于原型继承的语法糖。很多场景下我们需要把ES6类转换成更早版本支持的普通函数构造器,比如需要兼容不支持ES6的旧运行环境,或者需要深入理解原型继承的底层逻辑。转换过程并不是简单的语法替换,需要严格对应类的各个组成部分,才能保证转换后的构造器拥有和原类完全一致的行为,尤其是原型链继承的部分不能出现偏差。

如何正确将 ES6 类转换为传统函数构造器并保持原型链继承

ES6类与函数构造器的核心对应关系

要完成正确的转换,首先得明确ES6类各个部分在传统构造器模式中对应的实现位置。ES6类的constructor方法本质上就是函数构造器的函数体本身,当使用new关键字调用类的时候,执行的其实就是这个constructor里的逻辑。比如一个定义实例属性的constructor,转换后就是构造器函数内部直接给this挂载属性的逻辑,这部分是一一对应的,没有额外的语法差异。

ES6类中定义在类体里的方法,默认都是挂载到类的prototype属性上的,对应传统构造器就是给构造器的prototype对象添加方法。需要注意的是,ES6类里的方法默认是不可枚举的,而传统给prototype直接赋值的方法默认是可枚举的,如果需要完全对齐行为,还需要用Object.defineProperty来定义方法,设置enumerablefalse。另外ES6类里的静态方法和静态属性,是挂载到类本身上的,对应传统构造器就是直接给构造器函数对象添加属性,和原型对象没有关系。

我们可以通过一个简单的ES6类来看对应关系,比如下面这个定义:

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

  sayHello() {
    console.log(`Hello, I am ${this.name}, ${this.age} years old`);
  }

  static createDefault() {
    return new Person("Default", 0);
  }
}

Person.type = "Human";

这个类中,constructor里的nameage是实例属性,sayHello是原型方法,createDefaulttype是静态属性。转换的时候就要分别对应到构造器函数体、构造器prototype、构造器函数对象本身这三个位置,不能混淆。

基础ES6类转换为函数构造器的完整步骤

第一步先定义构造器函数,把原constructor的逻辑直接放到函数体中。如果原类有继承,还需要在构造器里调用父类的构造器,对应ES6类里的super()调用,传统写法里就是用父类构造器.call(this, 参数)的方式来实现,确保父类的实例属性被正确初始化到当前实例上。比如上面的Person类,构造器函数就可以写成:

function Person(name, age) {
  this.name = name;
  this.age = age;
}

第二步给构造器的prototype添加原型方法,对应ES6类里定义的非静态方法。因为ES6类的方法默认不可枚举,所以这里用Object.defineProperty来定义更严谨,如果需要兼容极老的环境不支持这个API,也可以直接赋值,只是枚举性会有差异。上面的sayHello方法转换后就是:

Object.defineProperty(Person.prototype, "sayHello", {
  value: function() {
    console.log("Hello, I am " + this.name + ", " + this.age + " years old");
  },
  enumerable: false,
  writable: true,
  configurable: true
});

第三步添加静态属性和方法,直接挂载到构造器函数上即可,和ES6类的静态成员对应。上面的静态createDefault方法和type属性转换后就是:

Person.createDefault = function() {
  return new Person("Default", 0);
};

Person.type = "Human";

到这里基础的无继承ES6类就转换完成了,我们可以测试一下转换后的构造器行为是否和原类一致:

var p = new Person("Tom", 18);
p.sayHello(); // 输出 Hello, I am Tom, 18 years old
console.log(Person.createDefault()); // 输出 Person { name: 'Default', age: 0 }
console.log(Person.type); // 输出 Human
console.log(p instanceof Person); // 输出 true
</script>

可以看到实例的行为、静态成员的访问、instanceof的判断都和原ES6类完全一致,说明基础转换是成功的。

带继承的ES6类转换时保持原型链的关键要点

当ES6类存在继承关系时,转换的复杂度会提升,核心是要正确设置原型链,保证instanceof判断正确,子类的原型方法能正确访问到父类的原型方法。ES6的extends关键字本质上做了两件事:一是把子类的原型对象的原型指向父类的原型对象,二是把子类的__proto__指向父类本身,对应传统写法里需要手动设置这两个指向。

假设我们有一个继承PersonStudent类,ES6写法如下:

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

  study() {
    console.log(`${this.name} is studying in grade ${this.grade}`);
  }
}

转换这个子类的时候,首先要定义子类的构造器函数,在构造器里用Person.call(this, name, age)来调用父类构造器,对应原类里的super()调用,这一步不能省略,否则父类的实例属性不会被初始化到子类实例上。然后需要设置子类的原型链,传统写法里不能直接把Student.prototype赋值成Person.prototype,否则修改子类原型会影响父类,正确的做法是创建一个以Person.prototype为原型的对象,赋值给Student.prototype,可以用Object.create(Person.prototype)来实现。

另外还需要把子类的prototype.constructor指回子类本身,因为用Object.create创建的对象,其constructor会继承自Person.prototype,指向Person,这会导致instanceof判断或者constructor访问出现错误。最后还要设置子类的__proto__指向父类,对应ES6类里子类本身的__proto__指向父类的行为,这一步是为了保证子类的静态成员继承正确。完整的转换代码如下:

function Student(name, age, grade) {
  Person.call(this, name, age);
  this.grade = grade;
}

// 设置子类原型链,原型对象的原型指向父类原型
Student.prototype = Object.create(Person.prototype);
// 修正constructor指向
Student.prototype.constructor = Student;
// 设置子类的__proto__指向父类,对应extends的静态继承
Object.setPrototypeOf(Student, Person);

// 添加子类的原型方法
Object.defineProperty(Student.prototype, "study", {
  value: function() {
    console.log(this.name + " is studying in grade " + this.grade);
  },
  enumerable: false,
  writable: true,
  configurable: true
});

转换完成后我们测试继承相关的行为:

var s = new Student("Jerry", 16, 10);
s.sayHello(); // 输出 Hello, I am Jerry, 16 years old,调用父类原型方法
s.study(); // 输出 Jerry is studying in grade 10,调用子类原型方法
console.log(s instanceof Student); // 输出 true
console.log(s instanceof Person); // 输出 true,原型链正确
console.log(Student.createDefault()); // 输出 Person { name: 'Default', age: 0 },静态方法继承正确

可以看到子类的实例既能访问父类的原型方法,也能访问自己的原型方法,instanceof对父类和子类的判断都正确,静态方法也正确继承了父类的静态方法,说明带继承的转换也完全保留了原有的原型链逻辑。如果省略了设置原型链或者constructor指向的步骤,就会出现s.sayHello不存在,或者s instanceof Person返回false的问题,这就是转换时最容易出错的地方。

ES6 类函数构造器原型链继承修改时间:2026-08-28 00:46:47

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。