JavaScript字面量、工厂函数与Class实例化的区别

作者:半夏

时间:2026-10-30

浏览:548

深入对比JavaScript中对象字面量、工厂函数与Class实例化的核心差异,详细解析内存模型、原型继承、this绑定与封装特性,提供清晰的工程选型策略。

文章目录

在 JavaScript 开发中,创建对象是日常开发的核心动作。对象字面量、工厂函数和 class 实例化代表了三种不同抽象层级的创建方式:字面量用于单次直接声明;工厂函数以普通函数包装对象装配过程并借闭包保护数据;class 实例化则依托原型链和构造函数规范实现结构化复用与高效继承。理解这三者在内存分配、原型链关联、this 指向及封装性上的差异,是编写健壮且高性能代码的关键基石。

一、三种对象创建机制的语法与运行机制

在探讨深层机制前,我们先通过同一个“用户对象”的具体示例,直观对比三者的基本代码形式及其核心执行特性。

1. 对象字面量(Object Literal)

对象字面量是 JavaScript 中最直接、语法开销最小的对象声明方式。它使用大括号 {} 立即在当前执行上下文中开辟内存空间,直接定义键值对。

// 对象字面量示例
const userLiteral = {
  name: "张三",
  role: "admin",
  sayHi() {
    return `你好,我是 ${this.name}`;
  }
};

console.log(userLiteral.sayHi()); // 示例输出: 你好,我是 张三
console.log(Object.getPrototypeOf(userLiteral) === Object.prototype); // true

字面量对象在定义时即刻完成实例化,其隐式原型 __proto__ 直接指向根对象 Object.prototype。该方式适用于单例、函数入参配置项或纯数据传输载体(DTO),但在需要生成多个同构对象时缺乏复用能力。

2. 工厂函数(Factory Function)

工厂函数是不需要使用 new 关键字调用的普通函数,其职责是接收参数、组装新对象并将其返回。它通过函数作用域自然形成闭包,从而提供了真正的私有状态保护能力。

// 工厂函数示例
function createMember(name, initialScore) {
  // 借助作用域闭包实现私有变量,外部无法直接访问或篡改
  let score = initialScore;

  return {
    name,
    addScore(points) {
      score += points;
    },
    getScore() {
      return score;
    },
    introduce() {
      return `成员 ${name} 当前积分: ${score}`;
    }
  };
}

const memberA = createMember("李四", 100);
memberA.addScore(20);
console.log(memberA.getScore()); // 示例输出: 120
console.log(memberA.score); // 示例输出: undefined(私有变量被封装)

工厂函数执行时,每次调用都会在堆内存中独立创建一个全新的匿名对象,并将方法绑定在新建的对象自身上。函数调用者无需关心 this 绑定的丢失问题,因为方法内部完全可以脱离 this 而依赖入参及闭包变量。

3. Class 实例化(Class Constructor)

ES6 引入的 class 语法是基于 JavaScript 原型继承机制的语法糖。使用 new 运算符调用类时,引擎会基于类的原型对象生成新实例,并在执行构造函数时将 this 绑定到新实例上。

// Class 实例化示例
class Account {
  #internalCode; // ES2022 私有字段

  constructor(owner, code) {
    this.owner = owner;
    this.#internalCode = code;
  }

  // 方法定义在 Account.prototype 上,所有实例共享同一份引用
  getSummary() {
    return `账户: ${this.owner}, 代码校验: ${this.#internalCode.slice(0, 3)}***`;
  }
}

const acc1 = new Account("王五", "SEC-8890");
const acc2 = new Account("赵六", "SEC-9921");

console.log(acc1.getSummary()); // 示例输出: 账户: 王五, 代码校验: SEC***
console.log(acc1.getSummary === acc2.getSummary); // true (共享原型方法)
console.log(acc1 instanceof Account); // true

二、内存模型与原型链绑定差异

三者在系统运行时的首要差异体现在内存分配策略与原型链挂载路径上。当创建数千甚至数万个对象时,这一底层差异会对应用程序的内存占用产生显著影响。

JavaScript字面量、工厂函数与Class实例化的区别
图 1:对象字面量、工厂函数与 Class 实例化的内存排布与原型链指向关系

从内存结构中可以得出以下几点确定性事实:

  • 方法复用效率:在工厂函数中,如果直接在返回的对象字面量内部声明函数,每次调用工厂都会在堆中重新分配函数实体。若创建 10,000 个实例,就会产生 10,000 个完全相同的函数对象;而 class 实例的所有原型方法仅在 Class.prototype 上保存一份,所有实例通过隐式原型链查找并调用,内存开销呈数量级降低。
  • 原型关系:字面量对象和普通工厂函数返回的对象,其隐式原型默认直接是 Object.prototype;而 class 生成的实例,其原型链明确为 instance → Class.prototype → Object.prototype,具备完备的类型标记与继承体系。

三、实例化生命周期与构造步骤推导

在底层运行逻辑上,字面量、工厂函数与 class 的执行链路截然不同。特别是 new 运算符与类构造函数配合时,涉及一套严格规范的初始化步骤。

JavaScript字面量、工厂函数与Class实例化的区别
图 2:三种对象声明在执行引擎中的生命周期演进与 new 操作符推导步骤

特别需要关注 new 运算符在 Class 实例化时的强制性约束:

  • ES6 明确规定:Class 内部的 [[Construct]] 内部方法存在,但没有 [[Call]] 方法。这意味着直接调用 Account("张三", "001") 会直接抛出 TypeError: Class constructor Account cannot be invoked without 'new',在语言层杜绝了漏写 new 导致污染全局作用域或 window/undefined 的隐患。
  • 相反,工厂函数是标准函数调用,无需 new。即便调用者习惯性地写了 new createMember(),由于函数内部显式返回了一个新对象,根据 JavaScript 规范,构造函数如果返回一个对象引用,new 表达式便直接返回该对象,因此不会破坏结果。

四、封装性、this 绑定与类型识别对比

在设计模块与领域对象时,工程师最关心的核心问题通常是:私有属性如何隐藏?方法中的 this 会不会丢失?能否通过类型检查?下表与图示直观呈现了这三者在这些关键维度的特性差异。

JavaScript字面量、工厂函数与Class实例化的区别
图 3:四项关键工程特性在三种机制下的横向能力矩阵

1. this 丢失的典型陷阱

在前端事件监听、定时器回调或 React/Vue 状态传递中,Class 的方法非常容易因丢失上下文导致报错:

class ButtonHandler {
  constructor(label) {
    this.label = label;
  }
  handleClick() {
    console.log(`点击了: ${this.label}`);
  }
}

const btn = new ButtonHandler("提交");
const clickFn = btn.handleClick;

// 独立调用时,严格模式下 this 为 undefined,执行报错: Cannot read properties of undefined
try {
  clickFn();
} catch (err) {
  console.log("捕获错误:", err.message); // 示例输出: Cannot read properties of undefined (reading 'label')
}

而如果使用工厂函数,通过闭包直接引用变量,则天然免疫 this 丢失:

function createButtonHandler(label) {
  return {
    handleClick: () => {
      // 直接通过词法作用域访问 label,无论如何解构或传递都不会丢失
      console.log(`点击了: ${label}`);
    }
  };
}

const safeBtn = createButtonHandler("提交");
const safeClick = safeBtn.handleClick;
safeClick(); // 正常输出: 点击了: 提交
架构设计警示:闭包与原型内存权衡

工厂函数带来的“免 this 困扰”和“绝对私有性”并非没有代价。每个工厂返回的对象方法都会形成独立的闭包。如果一个对象持有大量方法,并且需要在短时间内实例化数万个,垃圾回收器(GC)需要遍历和维护每个闭包作用域链,内存占用会比基于原型共享的 Class 高出数倍。

五、综合对比与工程选型决策

为了帮助开发者在实际项目中快速作出恰当的架构设计与技术选型,我们总结了一套明确的决策路径树。

JavaScript字面量、工厂函数与Class实例化的区别
图 4:面向实际项目需求的架构选型决策树与总结建议

选型速查表

对比维度 对象字面量 {} 工厂函数 factory() Class 实例化 new Class()
创建语法 简单字面量声明 普通函数返回对象 new 调用类构造函数
原型归属 Object.prototype Object.prototype Class.prototype
方法复用 无法直接复用 默认每实例一份(消耗堆内存) 挂载在原型,全局单份共享
私有化手段 不支持(仅软命名约定) 利用作用域闭包(硬私有) ES2022 #privateField 语法
类型判定 仅 typeof === 'object' 依赖鸭子类型或特征检查 支持 instanceof 原型链检测
最佳适用场景 函数配置入参、单例、简单数据集合 函数式编程、需要防御 this 丢失的模块 企业级业务实体、高频实例生成、重度继承框架

六、总结

JavaScript 提供的多样化对象构建方式并不是互相替代的关系,而是服务于不同的工程维度:

对象字面量是轻量、纯粹的数据描述形式;工厂函数赋予了开发者基于闭包的强封装性与摆脱 this 绑定的自由度,适合函数式设计和中轻量组合;Class 实例化则以严格的原型继承模型为基础,在内存共享、类型体系与大规模数据模型运算中提供了最优的运行效率与工程规范性。在日常开发中,应结合数据量级、上下文绑定风险与继承复杂度,选择最贴合当前场景的方案。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章