JavaScript 构造函数与对象创建模式深度解析
批量创建对象的需求:从工厂模式说起
在实际开发中,我们经常需要创建多个结构相似的对象。例如,在一个电商系统中,我们需要定义多个商品对象,每个商品都有名称、价格和库存等属性。
如果使用对象字面量的方式,代码会变得冗余且难以维护:
const product1 = {
title: "iPhone 15",
price: 5999,
stock: 100,
displayInfo: function() {
console.log(`产品:${this.title},价格:${this.price}`);
}
};
const product2 = {
title: "MacBook Pro",
price: 12999,
stock: 50,
displayInfo: function() {
console.log(`产品:${this.title},价格:${this.price}`);
}
};
为了解决重复代码的问题,我们可以使用工厂函数(Factory Function)。这是一种设计模式,通过封装一个函数来批量生产对象:
function createProduct(title, price, stock) {
const item = {};
item.title = title;
item.price = price;
item.stock = stock;
item.displayInfo = function() {
console.log(`产品:${this.title},价格:${this.price}`);
};
return item;
}
const p1 = createProduct("iPad", 3500, 200);
const p2 = createProduct("Watch", 2100, 150);
虽然工厂函数解决了代码复用问题,但它存在一个缺陷:创建出来的所有对象在类型检查时都指向 Object,无法通过类型标签区分它们具体属于哪一类业务对象。
构造函数与 new 关键字
在 JavaScript 中,构造函数是类的传统实现方式。从形式上看,构造函数就是一个普通函数,但习惯上首字母大写。当使用 new 操作符调用一个函数时,它就成为了构造函数。
执行 new 操作符时,JavaScript 引擎内部会完成以下五个步骤:
- 在内存中创建一个全新的空对象。
- 将该新对象的内部属性
[[Prototype]](即__proto__)指向构造函数的prototype属性。 - 将构造函数内部的
this绑定到这个新创建的对象上。 - 执行构造函数内部的代码(为新对象添加属性和方法)。
- 如果函数没有返回其他非原始类型(对象),则默认返回这个新创建的对象。
function SmartDevice(name, category) {
this.name = name;
this.category = category;
this.boot = function() {
console.log(`${this.name} 正在启动...`);
};
}
const phone = new SmartDevice("华为 Mate 60", "手机");
const tablet = new SmartDevice("小米平板", "平板");
类与对象的关系
类(构造函数)可以被视为一份"蓝图"或"模版"。它定义了未来生成的实例应该具备哪些特征(属性)和行为(方法)。而通过 new 关键字生成的实例则是根据这份蓝图构建出来的具体实体。

在 ES6 之后,JavaScript 引入了 class 关键字,使得类的声明更符合传统面向对象语言的语法习惯,但其底层机制依然是基于构造函数和原型链的。
函数的多重身份:作为对象与类方法
在 JavaScript 中,函数本身也是对象。这意味着我们可以给函数添加属性和方法。直接添加在构造函数上的方法被称为静态方法或类方法。
function Car(model) {
this.model = model;
}
// 实例方法:通过实例调用
Car.prototype.drive = function() {
console.log(`${this.model} 正在行驶`);
};
// 类方法(静态方法):直接通过类名调用
Car.isVehicle = function(obj) {
return obj instanceof Car;
};
const myCar = new Car("Tesla");
Car.isVehicle(myCar); // true
原始类型的包装对象(Wrapper Objects)
JavaScript 的原始类型(string, number, boolean, symbol, bigint)本不是对象,理应没有属性和方法。但我们可以观察到以下现象:
const str = "hello world";
console.log(str.length); // 11
console.log(str.toUpperCase()); // "HELLO WORLD"
这是因为 JavaScript 在访问原始类型的属性或方法时,会自动将其转换为对应的包装对象。其内部逻辑如下:
- 基于原始值临时创建一个对应的包装类实例(如
new String(str))。 - 调用该实例上的属性或方法,并获取返回值。
- 立即销毁这个临时的包装实例。
这种机制让原始类型兼具了轻量级和功能丰富的特点。需要注意的是,null 和 undefined 没有任何包装对象,因此尝试访问它们的属性会抛出错误。
const num = 123.456;
// 内部执行:new Number(num).toFixed(2)
console.log(num.toFixed(2)); // "123.46"
// 手动创建包装对象(通常不建议这么做)
const wrappedStr = new String("test");
console.log(typeof wrappedStr); // "object"
