JavaScript传统构造函数与ES6 class有什么区别
深入解析JavaScript传统构造函数与ES6 class的核心区别,涵盖语法糖本质、调用限制、方法枚举性、继承实现机制及原型链底层差异。
在 ECMAScript 6(ES2015)发布之前,JavaScript 依靠函数配合原型对象(prototype)来模拟面向对象中的“类”与继承机制;ES6 正式引入了 class 关键字。表面上看,class 是基于既有原型机制的一层语法糖,但在严格模式要求、调用约束、声明提升、方法枚举性以及继承底层实现(特别是子类 this 创建时机)等方面,两者存在多项具有实际运行时影响的本质区别。
一、定义语法与基本调用限制
在语法形式上,传统构造函数使用普通的 function 关键字声明,并在原型对象上逐个挂载方法;而 ES6 class 则使用声明式块结构,将构造逻辑与原型方法收束在同一块作用域中。
// 1. 传统构造函数方式
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
return "Hello, " + this.name;
};
// 2. ES6 class 方式
class User {
constructor(name) {
this.name = name;
}
sayHello() {
return `Hello, ${this.name}`;
}
}
两者在调用层面的关键区别在于调用保护:
- 普通调用防护:传统构造函数本质就是普通函数。如果开发者不小心遗漏了
new关键字(如Person('Tom')),在非严格模式下this会指向全局对象window或global,导致全局变量污染;即便在严格模式下,this为undefined也会引发报错,但语言规范本身并不禁止普通调用。而 ES6 的class内部包含[[IsClassConstructor]]: true内部标识,必须使用new关键字调用,直接作为普通函数执行会直接抛出原生TypeError: Class constructor User cannot be invoked without 'new'。 - 严格模式:ES6
class的整个类主体声明默认处于严格模式(strict mode),无需手动声明'use strict';而传统构造函数默认继承外层执行上下文的模式。
二、变量提升与暂时性死区(TDZ)
函数的声明提升是 JavaScript 初学者常遇到的特性,但在构造对象模型时,这种提升往往会导致代码可读性和设计混乱。构造函数与 class 在提升阶段表现完全不同。
传统函数声明会被完全提升到作用域顶部,这意味着在函数声明语句之前编写 new Dog() 可以正常工作。而 ES6 中的 class 声明与 let / const 行为一致,虽然在作用域创建时其标识符已知,但在执行流到达类声明之前,该标识符处于“暂时性死区”(Temporal Dead Zone, TDZ)。如果在类定义代码执行前调用,引擎会抛出 ReferenceError: Cannot access 'Cat' before initialization。
三、方法的可枚举性与属性描述符
在对象属性的遍历场景中,构造函数与 class 产生的原型对象有着截然不同的表现。这是两者在日常业务开发中容易产生细微 Bug 的关键原因之一。
// 传统构造函数方式
function Student(name) {
this.name = name;
}
Student.prototype.study = function() {};
// ES6 class 方式
class Scholar {
constructor(name) {
this.name = name;
}
study() {}
}
const s1 = new Student("Alice");
const s2 = new Scholar("Bob");
// 输出属性名
console.log(Object.keys(Student.prototype)); // ["study"]
console.log(Object.keys(Scholar.prototype)); // [] (空数组,因为不可枚举)
for (let key in s1) { console.log(key); } // 输出 "name", "study"
for (let key in s2) { console.log(key); } // 仅输出 "name"
当通过 Student.prototype.study = ... 添加方法时,该属性描述符的 enumerable 默认值为 true。这意味着通过 for...in 循环遍历实例时,该原型方法会被枚举出来。而 ES6 class 中定义的所有实例方法(包括 getter 和 setter),引擎都会自动将其 enumerable 设置为 false,与内置类型(如 Array.prototype.push)的行为保持高度一致,防止了遍历实例属性时意外带出原型方法的隐患。
四、原型继承链与 this 构建流程的核心差异
继承是两者差距最显著的技术环节。在 ES5 时代,标准继承方式是“寄生组合式继承”,而在 ES6 中使用 extends 和 super 关键字。
1. this 构建顺序颠倒
在 ES5 的传统继承中,实现的实质是:先创造子类的实例对象 this,然后再将父类的方法或属性添加到子类的 this 上面(例如通过 Parent.apply(this, arguments))。
而在 ES6 的 class 继承中,机制恰好相反:先将父类实例对象的属性和方法加到 this 上面(所以必须先调用 super() 方法),然后再用子类的构造函数修饰 this。子类如果定义了 constructor,在调用 super() 之前访问 this 会直接报 ReferenceError。
2. 原生内置对象的继承差异
这种 this 生成顺序的改变带来了一个极其重要的能力:能否正确继承原生的内置对象。在 ES5 中,像 Array、Error、RegExp 这类内置对象的内部属性(例如 Array 的内部插槽 [[ArrayData]] 和 length 的自动维护联动)是在原生构造函数创建 this 时初始化的。ES5 先建了普通子类 this,再执行 Array.apply(this),内置属性无法附加到这个普通对象上,因此自定义数组类无法像真实数组那样工作:
// ES5 模拟数组继承(失效示例)
function MyArray() {
Array.apply(this, arguments);
}
MyArray.prototype = Object.create(Array.prototype);
MyArray.prototype.constructor = MyArray;
var arr = new MyArray();
arr[0] = "val";
console.log(arr.length); // 仍然是 0,length 属性无法响应下标自动更新!
// ES6 class 继承原生数组(完美生效)
class ModernArray extends Array {
first() {
return this[0];
}
}
const mArr = new ModernArray();
mArr[0] = "val";
console.log(mArr.length); // 1,完全保持原生数组特性
3. 静态方法的继承链
ES6 class 的继承同时构建了两条原型链:
- 实例原型链:
SubClass.prototype.__proto__ === SuperClass.prototype,用于继承父类的原型方法。 - 构造函数原型链:
SubClass.__proto__ === SuperClass,用于继承父类的静态属性和静态方法(static)。
在传统 ES5 继承中,静态属性通常需要手动通过循环复制(Object.assign(SubClass, SuperClass))或者显式修改 SubClass.__proto__ = SuperClass,容易被遗漏。
五、综合差异总结与选型建议
为了直观掌握传统构造函数与 ES6 class 的差异,下表归纳了二者的关键属性:
| 对比维度 | 传统构造函数 (ES5) | ES6 Class |
|---|---|---|
| 语法形式 | 函数声明 + 外部挂载原型 | 集中式 class 块级声明 |
| 直接函数调用 | 允许调用(this 易引发全局污染) | 强制禁止,抛出 TypeError |
| 提升机制 | 存在完整函数声明提升 | 具有暂时性死区 (TDZ),不可提前访问 |
| 方法可枚举性 | 默认 enumerable: true |
默认 enumerable: false |
| 严格模式 | 需手动指定 "use strict" |
类代码块内自动进入严格模式 |
| this 初始化时机 | 子类先生成 this,再借调父类构造 | 父类先生成 this(需调 super()),子类再修饰 |
| 内置对象继承 | 无法完整继承原生特性(如 Array length) | 完全支持原生内置对象继承 |
总结:虽然 ES6 class 的底层基石依然是原型链,但它绝非简单的语法糖(Syntactic Sugar)。它在语言运行时层面增加了强约束(禁止直接调用、TDZ、默认不可枚举)并修正了继承流程(优先构建父类 this、双原型链支持),在提升代码规范性与安全性的同时,解决了传统 ES5 无法继承原生对象的重大历史遗留问题。在现代 JavaScript 与 TypeScript 研发中,除特殊元编程或轻量闭包工厂模式外,应优先使用 class 进行面向对象建模。
JS动态构建复杂嵌套JSON对象的方法
2026-11-05 15:41:52
JS负数取余为什么是负数?详解余数与模运算区别
2026-11-05 13:34:41
JS 解压出现乱码或报错怎么解决?
2026-11-05 12:32:38
JS 中 startsWith、indexOf 与正则判断字符串前缀的对比
2026-11-04 22:19:43
JS解析Excel后怎么将数据转换为JSON格式
2026-11-04 20:58:28








