JavaScript传统构造函数与ES6 class有什么区别

作者:纸飞机时间:2026-10-30 18:50:05浏览:185

深入解析JavaScript传统构造函数与ES6 class的核心区别,涵盖语法糖本质、调用限制、方法枚举性、继承实现机制及原型链底层差异。

在 ECMAScript 6(ES2015)发布之前,JavaScript 依靠函数配合原型对象(prototype)来模拟面向对象中的“类”与继承机制;ES6 正式引入了 class 关键字。表面上看,class 是基于既有原型机制的一层语法糖,但在严格模式要求、调用约束、声明提升、方法枚举性以及继承底层实现(特别是子类 this 创建时机)等方面,两者存在多项具有实际运行时影响的本质区别。

JavaScript传统构造函数与ES6 class有什么区别
图1:传统构造函数与 ES6 class 四大核心技术维度对比

一、定义语法与基本调用限制

在语法形式上,传统构造函数使用普通的 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 在提升阶段表现完全不同。

JavaScript传统构造函数与ES6 class有什么区别
图2:函数声明与类声明在预解析及执行阶段的作用域表现

传统函数声明会被完全提升到作用域顶部,这意味着在函数声明语句之前编写 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 关键字。

JavaScript传统构造函数与ES6 class有什么区别
图3:ES5 与 ES6 继承在 this 构建顺序上的根本倒置

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) 完全支持原生内置对象继承
JavaScript传统构造函数与ES6 class有什么区别
图4:从原型认知到工程选型的决策体系

总结:虽然 ES6 class 的底层基石依然是原型链,但它绝非简单的语法糖(Syntactic Sugar)。它在语言运行时层面增加了强约束(禁止直接调用、TDZ、默认不可枚举)并修正了继承流程(优先构建父类 this、双原型链支持),在提升代码规范性与安全性的同时,解决了传统 ES5 无法继承原生对象的重大历史遗留问题。在现代 JavaScript 与 TypeScript 研发中,除特殊元编程或轻量闭包工厂模式外,应优先使用 class 进行面向对象建模。

作者最新文章
相关文章
更多