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

前言背景

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

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

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

从一道面试题说起

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

javascript 复制代码
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 关键字,使类的定义更加简洁清晰:

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

使用方式:

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

ES5 构造函数写法

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

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

使用方式与 class 完全一致:

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

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

核心区别解析

1. 调用方式限制

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

javascript 复制代码
// 普通构造函数
function Computer2() {}

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

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

javascript 复制代码
// ES6 class
class Computer1 {}

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

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

2. 原型方法的可枚举性

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

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

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

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

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

3. 严格模式

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

javascript 复制代码
// 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 调用):

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

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

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

Babel 转译揭示的本质

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

javascript 复制代码
"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 实现:

javascript 复制代码
"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 语法,它不仅使代码更具可读性,也能通过严格模式和语法限制帮助我们编写更健壮的代码。

相关推荐
梦帮科技32 分钟前
Node.js配置生成器CLI工具开发实战
前端·人工智能·windows·前端框架·node.js·json
VT.馒头1 小时前
【力扣】2695. 包装数组
前端·javascript·算法·leetcode·职场和发展·typescript
css趣多多1 小时前
一个UI内置组件el-scrollbar
前端·javascript·vue.js
-凌凌漆-1 小时前
【vue】pinia中的值使用 v-model绑定出现[object Object]
javascript·vue.js·ecmascript
C澒2 小时前
前端整洁架构(Clean Architecture)实战解析:从理论到 Todo 项目落地
前端·架构·系统架构·前端框架
C澒2 小时前
Remesh 框架详解:基于 CQRS 的前端领域驱动设计方案
前端·架构·前端框架·状态模式
Charlie_lll2 小时前
学习Three.js–雪花
前端·three.js
onebyte8bits2 小时前
前端国际化(i18n)体系设计与工程化落地
前端·国际化·i18n·工程化
C澒2 小时前
前端分层架构实战:DDD 与 Clean Architecture 在大型业务系统中的落地路径与项目实践
前端·架构·系统架构·前端框架
BestSongC2 小时前
行人摔倒检测系统 - 前端文档(1)
前端·人工智能·目标检测