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

相关推荐
索迪迈科技2 小时前
CommonJS与ES6模块的区别
前端·ecmascript·es6
前端Hardy2 小时前
12个被低估的 CSS 特性,让前端开发效率翻倍!
前端·javascript·css
前端Hardy2 小时前
HTML&CSS:精美的3D折叠卡片悬停效果
前端·javascript·css
nightunderblackcat2 小时前
新手向:中文语言识别的进化之路
前端·javascript·easyui
Spider_Man3 小时前
打造属于你的前端沙盒 🎉
前端·typescript·github
用户47949283569153 小时前
🤫 你不知道的 JavaScript:`"👦🏻".length` 竟然不是 1?
前端·javascript·面试
掘金一周3 小时前
凌晨零点,一个TODO,差点把我们整个部门抬走 | 掘金一周 9.11
前端·人工智能·后端
用户8174413427483 小时前
kubernetes核心概念 Service
前端
东北南西3 小时前
Web Worker 从原理到实战 —— 把耗时工作搬到后台线程,避免页面卡顿
前端·javascript