深入解析:ES6 中 class 与普通构造器的区别

发布于:2025-09-11 ⋅ 阅读:(19) ⋅ 点赞:(0)

前言背景

在 JavaScript 的发展历程中,类的实现方式经历了从构造函数到 ES6 class 的演变。

很多开发者认为 class 只是构造函数的语法糖,但实际上两者在细节上存在诸多差异。

本文将从语法形式、内部机制、使用限制等多个维度,深入剖析 class 与普通构造器的区别。

从一道面试题说起

先看一道经典面试题:如何将以下 ES6 class 代码转换为等效的 ES5 实现?

class Example { 
  constructor(name) { 
    this.name = name;
  }
  init() { 
    const fun = () => { console.log(this.name) }
    fun(); 
  } 
}
const e = new Example('Hello');
e.init(); // 输出:Hello

要解答这道题,我们需要先理解 class 与构造函数的本质区别。让我们从两者的基本写法开始对比。

基本写法对比

ES6 class 写法

ES6 引入了 class 关键字,使类的定义更加简洁清晰:

class Computer {
    // 构造器
    constructor(name, price) {
        this.name = name;
        this.price = price;
    }
    // 原型方法
    showSth() {
        console.log(`这是一台${this.name}电脑`);
    }
    // 静态方法
    static comStruct() {
        console.log("电脑由显示器,主机,键鼠组成");
    }
}

使用方式:

const apple = new Computer("苹果", 15000);
console.log(apple.name); // 苹果
apple.showSth(); // 这是一台苹果电脑
Computer.comStruct(); // 电脑由显示器,主机,键鼠组成

ES5 构造函数写法

在 ES6 之前,我们通过构造函数模拟类的实现:

function Computer(name, price){
    this.name = name;
    this.price = price;
}
// 原型方法
Computer.prototype.showSth = function(){
    console.log(`这是一台${this.name}电脑`);
}
// 静态方法
Computer.comStruct = function(){
    console.log("电脑由显示器,主机,键鼠组成");
}

使用方式与 class 完全一致:

const apple = new Computer("苹果", 15000);
console.log(apple.name); // 苹果
apple.showSth(); // 这是一台苹果电脑
Computer.comStruct(); // 电脑由显示器,主机,键鼠组成

从表面看,两种写法实现了相同的功能,但深入细节会发现它们存在本质区别。

核心区别解析

1. 调用方式限制

普通构造函数本质是函数,既可以用new调用,也可以直接调用:

// 普通构造函数
function Computer2() {}

// 直接调用(不会报错)
const i = Computer2();
console.log(i); // undefined

而 class 必须使用new调用,直接调用会报错:

// ES6 class
class Computer1 {}

// 直接调用(报错)
Computer1(); 
// TypeError: Class constructor Computer1 cannot be invoked without 'new'

这是因为 class 内部做了调用方式检查,确保只能通过实例化方式使用。

2. 原型方法的可枚举性

普通构造函数的原型方法默认是可枚举的:

const apple = new Computer2("苹果", 15000);
for(var i in apple){
    console.log(i); 
}
// 输出:
// name
// price
// showSth

class 的原型方法默认是不可枚举的:

const huawei = new Computer1("华为", 12000);
for(var i in huawei){
    console.log(i); 
}
// 输出:
// name
// price

这种差异会影响对象遍历的结果,在某些场景下(如对象拷贝)需要特别注意。

3. 严格模式

class 内部默认运行在严格模式下,而普通构造函数默认在非严格模式下运行:

// class中定义重复参数(报错)
class Computer1 {
    showSth(i,i) { // SyntaxError: Duplicate parameter name not allowed in this context
        console.log(`这是一台${this.name}电脑`);
    }
}

// 普通构造函数中定义重复参数(不报错)
Computer2.prototype.showSth = function(j,j){
    console.log(`这是一台${this.name}电脑`);
}

严格模式带来了更多的语法限制,如禁止重复参数、禁止未声明变量赋值等,有助于编写更规范的代码。

4. 原型方法的构造器限制

普通构造函数的原型方法可以作为构造函数使用(通过 new 调用):

const apple = new Computer2("苹果", 15000);
const i = new apple.showSth(); // 可以执行
console.log(i); // {}

class 的原型方法不能作为构造函数使用:

const huawei = new Computer1("华为", 12000);
const i = new huawei.showSth(); 
// TypeError: huawei.showSth is not a constructor

Babel 转译揭示的本质

通过 Babel 将 class 转译为 ES5 代码,可以更清晰地看到 class 的内部实现机制:

"use strict";
function _classCallCheck(instance, Constructor) {
    if (!(instance instanceof Constructor)) {
        throw new TypeError("Cannot call a class as a function");
    }
}

function _defineProperties(target, props) {
    for (var i = 0; i < props.length; i++) {
        var descriptor = props[i];
        descriptor.enumerable = descriptor.enumerable || false;
        descriptor.configurable = true;
        if ("value" in descriptor)
            descriptor.writable = true;
        Object.defineProperty(target, descriptor.key, descriptor);
    }
}

function _createClass(Constructor, protoProps, staticProps) {
    if (protoProps)
        _defineProperties(Constructor.prototype, protoProps);
    if (staticProps)
        _defineProperties(Constructor, staticProps);
    return Constructor;
}

var Computer = /*#__PURE__*/function () {
    function Computer(name, price) {
        _classCallCheck(this, Computer);
        this.name = name;
        this.price = price;
    }

    _createClass(Computer, [{
        key: "showSth",
        value: function showSth() {
            console.log(`这是一台${this.name}电脑`);
        }
    }], [{
        key: "comStruct",
        value: function comStruct() {
            console.log("电脑由显示器,主机,键鼠组成");
        }
    }]);

    return Computer;
}();

转译后的代码揭示了几个关键函数的作用:

  1. _classCallCheck:检查调用方式,确保只能通过 new 实例化
  2. _defineProperties:定义属性时设置描述符(控制可枚举性等)
  3. _createClass:将原型方法和静态方法挂载到对应位置

这些函数共同实现了 class 的特殊行为,使其与普通构造函数区分开来。

面试题解答

回到开头的面试题,将 ES6 class 转换为 ES5 的关键在于:

  • 实现 class 的调用检查
  • 正确处理原型方法的可枚举性
  • 处理箭头函数的 this 绑定(箭头函数会捕获当前上下文的 this)

完整的 ES5 实现:

"use strict";

function _classCallCheck(instance, Constructor) {
    if (!(instance instanceof Constructor)) {
        throw new TypeError("Cannot call a class as a function");
    }
}

function _defineProperties(target, props) {
    for (var i = 0; i < props.length; i++) {
        var descriptor = props[i];
        descriptor.enumerable = descriptor.enumerable || false;
        descriptor.configurable = true;
        if ("value" in descriptor)
            descriptor.writable = true;
        Object.defineProperty(target, descriptor.key, descriptor);
    }
}

function _createClass(Constructor, protoProps, staticProps) {
    if (protoProps)
        _defineProperties(Constructor.prototype, protoProps);
    if (staticProps)
        _defineProperties(Constructor, staticProps);
    return Constructor;
}

var Example = /*#__PURE__*/function () {
   function Example(name) {
        _classCallCheck(this, Example);
        this.name = name;
   }

   _createClass(Example, [{
        key: "init",
        value: function init() {
            var _this = this; // 保存this引用,模拟箭头函数的this绑定
            
            var fun = function fun() {
                console.log(_this.name);
            };
            
            fun();
        }
   }]);

   return Example;
}();

var e = new Example('Hello');
e.init(); // 输出:Hello

总结

class 虽然在表面上看起来是构造函数的语法糖,但实际上:

  1. class 有更严格的调用限制(必须使用 new)
  2. class 的原型方法默认不可枚举
  3. class 内部自动启用严格模式
  4. class 的原型方法不能作为构造函数使用
  5. class 提供了更清晰的语法结构和继承机制

理解这些差异有助于我们在实际开发中选择合适的方式,并避免因误解而产生的 bug。在现代 JavaScript 开发中,推荐使用 class 语法,它不仅使代码更具可读性,也能通过严格模式和语法限制帮助我们编写更健壮的代码。


网站公告

今日签到

点亮在社区的每一天
去签到