JavaScript构造函数与普通函数的写法区别

作者:半夏

时间:2026-10-30

浏览:469

深入解析JavaScript中构造函数与普通函数的写法区别,从命名规范、声明方式、this指向、return返回值机制、原型链挂载到ES6 Class演进,全面掌握两者的核心差异与工程实践。

文章目录

在 JavaScript 中,构造函数(Constructor Function)与普通函数在本质上都属于函数对象,底层均由 Function 构造而成。然而,它们在代码书写风格、调用机制、内部 this 绑定逻辑以及返回值处理规则上存在根本差异。普通函数主要用于封装一段独立、可复用的逻辑并产出结果;而构造函数则作为创建对象的模板,通过配合 new 关键字,负责初始化新对象的属性并在原型链上建立关联。理解两者在写法与运行机制上的分水岭,是写出健壮前端代码和深入理解面向对象特性的关键基础。

一、命名规范与声明方式的差异

在语法声明层面,构造函数与普通函数最直观的区别首先体现在命名约定与支持的声明形式上。

1. 命名约定:小驼峰与大驼峰

JavaScript 引擎并不会从语法上强行检测一个函数是构造函数还是普通函数,因此开发者约定通过首字母的大小写来建立直观契约:

  • 普通函数采用小驼峰命名法(camelCase):名称通常体现动作或行为,例如 calculateSum、fetchUserData、formatDate。
  • 构造函数采用大驼峰命名法(PascalCase):名称通常代表实体类型或名词,例如 User、ModalDialog、EventEmitter。大写首字母明确提醒调用者必须使用 new 关键字执行。

2. 箭头函数的可用性限制

在函数声明方式上,普通函数可以使用函数声明式、函数表达式,也可以使用 ES6 引入的箭头函数。但构造函数绝对不能使用箭头函数编写:

  • 箭头函数内部没有自己的 this,其 this 继承自主调上下文。
  • 箭头函数没有 prototype 属性。
  • 箭头函数内部没有 [[Construct]] 内部方法,尝试对箭头函数使用 new 会直接抛出 TypeError: ... is not a constructor 运行时错误。
// 【普通函数】支持箭头函数与常规声明
const getFullGreeting = (name) => {
  return `Hello, ${name}!`;
};

function calculatePrice(base, taxRate) {
  return base * (1 + taxRate);
}

// 【构造函数】必须使用常规函数定义,不能使用箭头函数
function UserAccount(username, email) {
  this.username = username;
  this.email = email;
  this.isActive = true;
}

// 错误示范:箭头函数用作构造函数将抛错
// const Point = (x, y) => { this.x = x; this.y = y; };
// new Point(10, 20); // Uncaught TypeError: Point is not a constructor

二、内部逻辑与 this 绑定的写法区别

进入函数体内部,两者的编写焦点完全不同。普通函数通常处理入参并返回计算结果,而构造函数的核心职责是“对实例对象进行属性赋值”。

普通函数中的 this 并不固定,取决于调用时的执行环境。在非严格模式下独立调用普通函数时,this 默认指向全局对象(浏览器环境下为 window);在严格模式('use strict')下,this 则为 undefined。

构造函数内部则高度依赖 this。当使用 new 调用时,执行环境会自动创建一个全新的空对象,并将该构造函数体内的 this 绑定到这个新建的实例上。开发者在构造函数内部书写 this.property = value,本质上就是在为新建实例写入私有属性。

// 普通函数:内部逻辑围绕局部变量与逻辑运算展开
function formatUserProfile(user) {
  // this 在此场景下无意义,若直接访问 this.name 往往会导致非预期行为
  const cleanName = user.name.trim();
  const statusText = user.isActive ? '活跃' : '离线';
  return `用户: ${cleanName} [${statusText}]`;
}

// 构造函数:内部集中通过 this 为新建实例赋值
function ProductItem(title, price, stock) {
  this.title = title;
  this.price = price;
  this.stock = stock;
  this.createdAt = new Date();
}
JavaScript构造函数与普通函数的写法区别
图 1:普通函数调用与构造函数 new 实例化执行流程差异推导

三、return 返回值机制与写法陷阱

返回值机制是两者容易混淆且最体现差异的地方。普通函数完全遵从代码中的 return 表达式,而构造函数存在隐式返回对象机制。

1. 普通函数的 return 规则

普通函数如果不写 return,或者写了 return;,执行结束时默认返回 undefined。若写了返回值,不论是原始类型(如数字、字符串、布尔值)还是引用对象,函数均严格返回该值。

2. 构造函数的 return 规则

在构造函数中,通常不需要编写 return 语句,因为 JavaScript 引擎在执行完构造函数体后,会自动将新建的 this 实例返回出来。

如果开发者显式编写了 return,则会出现双重判断规则:

  • 返回原始值(Primitive):若返回数字、字符串、布尔值、null 或 undefined,该返回值会被引擎直接忽略,最终依然正常返回实例 this。
  • 返回对象类型(Object):若显式返回一个对象、数组或函数,该对象会直接覆盖默认的新建实例,作为 new 表达式的执行结果返回。
// 案例 1:构造函数显式返回原始值(无效覆盖)
function Car(model) {
  this.model = model;
  return '五菱宏光'; // 尝试返回字符串(原始值)
}
const myCar = new Car('Model 3');
console.log(myCar); // 输出: Car { model: 'Model 3' }(原始返回值被忽略)

// 案例 2:构造函数显式返回对象(强制覆盖)
function SecretBox(secret) {
  this.secret = secret;
  return { customKey: '劫持返回' }; // 返回了新对象
}
const box = new SecretBox('123456');
console.log(box); // 输出: { customKey: '劫持返回' },原 this 实例被丢弃
写法规范提醒

在编写构造函数时,除非有单例模式或特定代理对象的拦截需求,否则应当保持纯粹的 this.xxx = yyy 赋值,切勿书写多余的 return 语句,避免破坏原型链绑定与类型直观性。

四、方法定义位置与原型链架构

另一个重要的写法差异在于“方法(行为)写在哪里”。普通函数通常不涉及实例化与方法继承,而构造函数必须慎重考虑内存占用与复用方式。

如果直接在构造函数体内为 this 挂载方法,每次使用 new 生成实例时,都会在内存中为该方法开辟一块全新的独立函数空间。如果有 10,000 个实例,就会在内存中复制 10,000 个功能完全相同的函数副本,造成严重的内存浪费。

正确的构造函数写法是将可共享的方法挂载到构造函数的 prototype 原型对象上。所有实例的隐式原型 __proto__ 都指向同一个 prototype 对象,不仅节省内存,还支持运行时动态拓展与继承。

JavaScript构造函数与普通函数的写法区别
图 2:构造函数内联方法与 Prototype 原型链内存结构对比
// 推荐的标准构造函数组合写法
function Customer(id, nickname) {
  // 1. 实例独享属性定义在函数体内
  this.id = id;
  this.nickname = nickname;
}

// 2. 共享行为方法挂载在 prototype 上
Customer.prototype.sayHello = function () {
  return `您好,我是客户 ${this.nickname} (ID: ${this.id})`;
};

const c1 = new Customer(101, 'Alice');
const c2 = new Customer(102, 'Bob');
console.log(c1.sayHello === c2.sayHello); // 输出: true(复用相同函数引用)

五、防漏 new 机制与安全调用写法

如果使用者在调用构造函数时无意中漏写了 new 关键字(如直接调用 Customer(101, 'Alice')),在非严格模式下,this 将直接指向全局对象 window,原本应赋给实例的属性会污染全局作用域,同时函数的返回值会变成 undefined。

为了提高工程代码的容错性,针对构造函数往往有两种经典的防御性写法:

1. 基于 instanceof 的自适应实例化

通过检查当前 this 是否由该构造函数创建,如果不是,则主动在内部执行 new 并返回:

function SafePerson(name, age) {
  if (!(this instanceof SafePerson)) {
    return new SafePerson(name, age);
  }
  this.name = name;
  this.age = age;
}

// 无论是否带 new,都能安全获得正确实例
const p1 = new SafePerson('Tom', 18);
const p2 = SafePerson('Jerry', 20); // 内部自动修正,不会污染全局

2. ES6 提供的 new.target 元属性

在 ES6 及以上版本,可以在函数体内通过 new.target 属性精确判断该函数是否通过 new 调用。当直接以普通方式调用时,new.target 为 undefined:

function ModernWidget(config) {
  if (!new.target) {
    throw new Error('ModernWidget 是构造函数,必须使用 new 关键字进行实例化!');
  }
  this.config = config;
}

六、构造函数与普通函数多维度对比

为了让不同场景下的选型与书写要求更加明确,我们将两者的关键技术特征整理为矩阵对比:

JavaScript构造函数与普通函数的写法区别
图 3:JavaScript 构造函数与普通函数的核心技术差异对比矩阵

七、现代演进:ES6 Class 语法糖的对应关系

随着 ECMAScript 2015(ES6)标准的普及,JavaScript 引入了 class 关键字。虽然底层依然基于原型链与构造函数运行,但在写法规范上对开发者施加了更为严格且清晰的约束:

  • 必须使用 new 调用:直接以函数方式调用 class 会在编译/运行期直接报错 Class constructor cannot be invoked without 'new',从源头消除了防漏 new 的心智负担。
  • 属性与原型方法分离书写:在 constructor 内部完成实例自有属性的赋值,而在类内部直接声明的方法默认会被编译挂载到原型链上,避免了反复手写 FunctionName.prototype.method = ... 的繁琐操作。
// ES6 Class 的现代等价写法
class ModernMember {
  // 对应构造函数体内的 this 赋值
  constructor(name, level) {
    this.name = name;
    this.level = level;
  }

  // 默认挂载到 ModernMember.prototype 上
  getDiscountRate() {
    return this.level === 'VIP' ? 0.8 : 0.95;
  }
}

const member = new ModernMember('Bob', 'VIP');
console.log(member.getDiscountRate()); // 0.8

八、总结与选型决策

在实际项目开发中,选择普通函数还是构造函数(或其现代形态 ES6 Class),应当根据具体的业务任务类型来决定:

JavaScript构造函数与普通函数的写法区别
图 4:普通函数与构造函数(及 ES6 Class)的场景选择与编写决策树

综上所述,构造函数与普通函数的写法差异不仅是“大写首字母”和“搭配 new”的形式区别,更牵涉到作用域管理、内存复用和原型链体系的协同运作。在编写代码时,明确两者的职责边界,严格遵守命名与书写规范,能够帮助我们构建出结构清晰、执行高效的 JavaScript 应用。

实用工具推荐

电子包浆

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

使用 ›
图片压缩

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

使用 ›
相关文章