什么是构造函数
构造函数 (Constructor Function) 是用于创建和初始化对象的特殊函数.
通过 new 关键字调用构造函数, 可以创建具有相同属性和方法的多个对象实例.
构造函数的类型
JavaScript 中的构造函数分为两类:
1. 内置构造函数
JavaScript 提供的系统构造函数, 用于创建基本类型的包装对象和内置对象:
            
            
              javascript
              
              
            
          
          // 基本类型包装对象
const num = new Number(42);
const str = new String("hello");
const bool = new Boolean(true);
// 内置对象
const obj = new Object();
const arr = new Array(1, 2, 3);
const fn = new Function("a", "b", "return a + b");
const date = new Date();
const regex = new RegExp("\\d+");
// 注意: Symbol 和 BigInt 不能用 new 调用
// const sym = new Symbol(); // TypeError: Symbol is not a constructor
// const bigInt = new BigInt(123); // TypeError: BigInt is not a constructor2. 自定义构造函数
开发者定义的构造函数, 用于创建自定义类型的对象:
            
            
              javascript
              
              
            
          
          // 构造函数使用大驼峰命名 (PascalCase)
function Person(name, age) {
    this.name = name;
    this.age = age;
}
// 普通函数使用小驼峰命名 (camelCase)
function calculateAge(birthYear) {
    return new Date().getFullYear() - birthYear;
}命名规范
- 构造函数 : 使用大驼峰命名 (PascalCase), 如 Person,UserAccount,OrderManager
- 普通函数 : 使用小驼峰命名 (camelCase), 如 getUserInfo,calculateTotal,handleClick
这种命名约定帮助开发者快速识别函数的用途.
new 运算符的工作原理
使用 new 关键字调用构造函数时, JavaScript 引擎会执行以下步骤:
执行步骤
            
            
              javascript
              
              
            
          
          function Person(name) {
    // 步骤 1: 创建空对象 (引擎自动执行)
    // const newObj = {};
    // 步骤 2: 将空对象的 [[Prototype]] 指向构造函数的 prototype (引擎自动执行)
    // Object.setPrototypeOf(newObj, Person.prototype);
    // 步骤 3: 将 this 绑定到新对象 (引擎自动执行)
    // 从这里开始, this 指向新创建的对象
    // 步骤 4: 执行构造函数代码
    this.name = name;
    this.greet = function () {
        console.log(`Hello, I'm ${this.name}`);
    };
    // 步骤 5: 返回新对象 (引擎自动执行, 除非显式返回对象)
    // return newObj;
}
const person = new Person("Alice");
console.log(person.name); // Alice
person.greet(); // Hello, I'm Alice不使用 new 的后果
如果不使用 new 关键字, 构造函数会作为普通函数执行:
            
            
              javascript
              
              
            
          
          function Person(name) {
    this.name = name;
    console.log("this", this); // 在浏览器中指向 window, 在严格模式下是 undefined
}
// ❌ 错误用法: 不使用 new
const person1 = Person("Charlie");
console.log(person1); // undefined (函数没有返回值)
console.log(window.name); // 'Charlie' (污染了全局对象!)
// ✅ 正确用法: 使用 new
const person2 = new Person("David");
console.log(person2); // Person { name: 'David' }严格模式下的保护:
            
            
              javascript
              
              
            
          
          "use strict";
function Person(name) {
    // 严格模式下, this 为 undefined
    this.name = name; // TypeError: Cannot set property 'name' of undefined
}
const person = Person("Eve"); // 抛出错误构造函数的基本使用
定义与调用
            
            
              javascript
              
              
            
          
          // 定义构造函数
function User(username, email) {
    // 实例属性
    this.username = username;
    this.email = email;
    this.createdAt = new Date();
}
// 使用 new 创建实例
const user1 = new User("alice", "alice@example.com");
const user2 = new User("bob", "bob@example.com");
console.log(user1.username); // alice
console.log(user2.username); // bob
// 验证实例类型
console.log(user1 instanceof User); // true
console.log(user1.constructor === User); // true参数传递与默认值
ES6 参数默认值:
            
            
              javascript
              
              
            
          
          function Person(name = "Anonymous", age = 0) {
    this.name = name;
    this.age = age;
}
const p1 = new Person("Alice", 25);
const p2 = new Person("Bob");
const p3 = new Person();
console.log(p1); // Person { name: 'Alice', age: 25 }
console.log(p2); // Person { name: 'Bob', age: 0 }
console.log(p3); // Person { name: 'Anonymous', age: 0 }对象参数解构:
            
            
              javascript
              
              
            
          
          function Person({ name = "Anonymous", age = 0, email = "no-email" } = {}) {
    this.name = name;
    this.age = age;
    this.email = email;
}
// 灵活的调用方式
const p1 = new Person({ name: "Alice", age: 25 });
const p2 = new Person({ email: "bob@example.com" });
const p3 = new Person();
console.log(p1); // Person { name: 'Alice', age: 25, email: 'no-email' }
console.log(p2); // Person { name: 'Anonymous', age: 0, email: 'bob@example.com' }
console.log(p3); // Person { name: 'Anonymous', age: 0, email: 'no-email' }this 的指向
            
            
              javascript
              
              
            
          
          function Person(name) {
    this.name = name;
    // this 指向新创建的实例对象
    console.log("Inside constructor:", this);
    this.regularMethod = function () {
        // 普通函数中的 this 指向调用者
        console.log("Regular method:", this);
    };
    this.arrowMethod = () => {
        // 箭头函数中的 this 继承自构造函数的 this
        console.log("Arrow method:", this);
    };
}
const person = new Person("Alice");
// Inside constructor: Person { name: 'Alice' }
person.regularMethod();
// Regular method: Person { name: 'Alice', ... }
person.arrowMethod();
// Arrow method: Person { name: 'Alice', ... }
// 注意: 普通函数的 this 可以被改变
const borrowed = person.regularMethod;
borrowed(); // Regular method: undefined (严格模式) 或 window (非严格模式)
// 箭头函数的 this 不会改变
const borrowedArrow = person.arrowMethod;
borrowedArrow(); // Arrow method: Person { name: 'Alice', ... }构造函数的返回值
默认返回行为
构造函数默认返回新创建的实例对象 (this), 即使没有显式写 return 语句.
            
            
              javascript
              
              
            
          
          function Person(name) {
    this.name = name;
    // 隐式返回 this
}
const person = new Person("Alice");
console.log(person); // Person { name: 'Alice' }
console.log(person instanceof Person); // true显式返回的影响
构造函数的返回值行为遵循以下规则:
规则 1: 返回原始值 (Primitive) - 被忽略
            
            
              javascript
              
              
            
          
          function PersonWithNumber(name) {
    this.name = name;
    return 42; // 返回数字 (原始值)
}
function PersonWithString(name) {
    this.name = name;
    return "hello"; // 返回字符串 (原始值)
}
function PersonWithNull(name) {
    this.name = name;
    return null; // 返回 null (原始值)
}
// 所有原始值返回都会被忽略, 依然返回实例对象
const p1 = new PersonWithNumber("Alice");
const p2 = new PersonWithString("Bob");
const p3 = new PersonWithNull("David");
console.log(p1); // PersonWithNumber { name: 'Alice' }
console.log(p2); // PersonWithString { name: 'Bob' }
console.log(p3); // PersonWithNull { name: 'David' }规则 2: 返回对象 (Object) - 覆盖默认返回值
            
            
              javascript
              
              
            
          
          function PersonWithArray(name) {
    this.name = name;
    return [1, 2, 3]; // 返回数组 (对象)
}
function PersonWithObject(name) {
    this.name = name;
    return { customName: "Custom", value: 100 }; // 返回对象
}
function PersonWithFunction(name) {
    this.name = name;
    return function () {
        return "I am a function";
    }; // 返回函数 (对象)
}
// 对象类型的返回值会覆盖默认的实例对象
const p1 = new PersonWithArray("Alice");
const p2 = new PersonWithObject("Bob");
const p3 = new PersonWithFunction("Charlie");
console.log(p1); // [1, 2, 3]
console.log(p1 instanceof PersonWithArray); // false (不再是实例)
console.log(p2); // { customName: 'Custom', value: 100 }
console.log(p2 instanceof PersonWithObject); // false
console.log(p3); // [Function (anonymous)]
console.log(p3()); // I am a function
console.log(p3 instanceof PersonWithFunction); // false实际应用场景
场景 1: 单例模式
            
            
              javascript
              
              
            
          
          function Singleton(name) {
    // 如果已经存在实例, 返回已有实例
    if (Singleton.instance) {
        return Singleton.instance;
    }
    this.name = name;
    this.timestamp = Date.now();
    // 保存实例引用
    Singleton.instance = this;
}
const s1 = new Singleton("First");
const s2 = new Singleton("Second");
console.log(s1 === s2); // true (同一个实例)
console.log(s1.name); // First
console.log(s2.name); // First (不是 Second)场景 2: 条件性返回不同对象
            
            
              javascript
              
              
            
          
          function UserFactory(type, name) {
    this.name = name;
    // 根据类型返回不同的对象结构
    if (type === "admin") {
        return {
            name: name,
            role: "admin",
            permissions: ["read", "write", "delete"],
        };
    }
    if (type === "guest") {
        return {
            name: name,
            role: "guest",
            permissions: ["read"],
        };
    }
    // 默认返回普通用户实例
}
const admin = new UserFactory("admin", "Alice");
const guest = new UserFactory("guest", "Bob");
const user = new UserFactory("user", "Charlie");
console.log(admin); // { name: 'Alice', role: 'admin', permissions: [...] }
console.log(guest); // { name: 'Bob', role: 'guest', permissions: [...] }
console.log(user); // UserFactory { name: 'Charlie' }实例属性与方法
实例独有属性
每次使用 new 调用构造函数时, 都会创建一个新的独立对象:
            
            
              javascript
              
              
            
          
          function Counter() {
    this.count = 0;
    this.increment = function () {
        this.count++;
        console.log(`Count: ${this.count}`);
    };
}
const counter1 = new Counter();
const counter2 = new Counter();
counter1.increment(); // Count: 1
counter1.increment(); // Count: 2
counter2.increment(); // Count: 1
counter2.increment(); // Count: 2
console.log(counter1.count); // 2
console.log(counter2.count); // 2
// 两个实例互不影响
console.log(counter1 === counter2); // false
console.log(counter1.increment === counter2.increment); // false (每个实例都有自己的方法)方法定义的性能问题
⚠️ 性能警告: 在构造函数中直接定义方法会导致每个实例都创建新的函数对象, 浪费内存.
            
            
              javascript
              
              
            
          
          function Person(name) {
    this.name = name;
    // ❌ 不推荐: 每次创建实例都会创建新的函数对象
    this.greet = function () {
        console.log(`Hello, I'm ${this.name}`);
    };
}
const p1 = new Person("Alice");
const p2 = new Person("Bob");
// 两个实例的 greet 方法是不同的函数对象
console.log(p1.greet === p2.greet); // false
// 每个实例都占用独立的内存空间
console.log(p1.greet.toString() === p2.greet.toString()); // true (代码相同)
// 但它们是不同的对象, 占用不同的内存✅ 解决方案: 使用原型
            
            
              javascript
              
              
            
          
          function Person(name) {
    this.name = name;
}
// 将共享方法定义在原型上
Person.prototype.greet = function () {
    console.log(`Hello, I'm ${this.name}`);
};
const p1 = new Person("Alice");
const p2 = new Person("Bob");
// 所有实例共享同一个方法
console.log(p1.greet === p2.greet); // true (节省内存)
p1.greet(); // Hello, I'm Alice
p2.greet(); // Hello, I'm Bobnew.target 元属性
new.target 是 ES2015 (ES6) 引入的元属性, 用于检测函数是否通过 new 运算符调用.
基本使用
            
            
              javascript
              
              
            
          
          function Person(name) {
    // 检测是否使用 new 调用
    if (!new.target) {
        throw new Error("Person must be called with new");
    }
    this.name = name;
}
// ✅ 正确使用
const person1 = new Person("Alice");
console.log(person1); // Person { name: 'Alice' }
// ❌ 错误使用
try {
    const person2 = Person("Bob"); // 抛出错误
} catch (e) {
    console.error(e.message); // Person must be called with new
}自动修正
            
            
              javascript
              
              
            
          
          function Person(name) {
    // 如果忘记使用 new, 自动补救
    if (!new.target) {
        return new Person(name);
    }
    this.name = name;
}
const person1 = new Person("Alice"); // 使用 new
const person2 = Person("Bob"); // 忘记使用 new, 自动补救
console.log(person1); // Person { name: 'Alice' }
console.log(person2); // Person { name: 'Bob' }
console.log(person2 instanceof Person); // true检测继承调用
new.target 在继承场景中指向实际被 new 调用的子类构造函数:
            
            
              javascript
              
              
            
          
          function Person(name) {
    this.name = name;
    console.log("new.target:", new.target.name);
}
function Employee(name, job) {
    Person.call(this, name);
    this.job = job;
}
const person = new Person("Alice");
// new.target: Person
const employee = new Employee("Bob", "Engineer");
// new.target: Employee手动实现 new 运算符
通过手动实现 new 运算符的逻辑, 可以深入理解其工作原理.
完整实现
            
            
              javascript
              
              
            
          
          function myNew(constructor, ...args) {
    // 参数验证: 确保 constructor 是函数
    if (typeof constructor !== "function") {
        throw new TypeError("First argument must be a constructor function");
    }
    // 步骤 1 & 2: 创建对象并设置原型
    const obj = Object.create(constructor.prototype);
    // 步骤 3 & 4: 绑定 this 并执行构造函数
    const result = constructor.apply(obj, args);
    // 步骤 5: 判断返回值
    // 如果构造函数返回对象, 则返回该对象; 否则返回新创建的对象
    const isObject = result !== null && typeof result === "object";
    const isFunction = typeof result === "function";
    return isObject || isFunction ? result : obj;
}常见陷阱与注意事项
陷阱 1: 忘记使用 new
            
            
              javascript
              
              
            
          
          function Person(name) {
    this.name = name;
}
// ❌ 错误: 忘记 new
const person = Person("Alice");
console.log(person); // undefined
console.log(window.name); // 'Alice' (污染全局对象)解决方案:
- 使用 new.target检测
- 使用严格模式
- 使用 ES6 Class (强制使用 new)
陷阱 2: 构造函数返回对象覆盖实例
            
            
              javascript
              
              
            
          
          function Person(name) {
    this.name = name;
    return { custom: "object" }; // 覆盖默认返回值
}
const person = new Person("Alice");
console.log(person); // { custom: 'object' }
console.log(person.name); // undefined
console.log(person instanceof Person); // false陷阱 3: 箭头函数不能作为构造函数
            
            
              javascript
              
              
            
          
          const Person = (name) => {
    this.name = name;
};
try {
    const person = new Person("Alice"); // TypeError: Person is not a constructor
} catch (e) {
    console.error(e.message);
}原因:
- 箭头函数没有自己的 this
- 箭头函数没有 prototype属性
- 箭头函数没有 [[Construct]]内部方法
陷阱 4: 在构造函数中定义方法导致内存浪费
            
            
              javascript
              
              
            
          
          // ❌ 不推荐: 每个实例都创建新函数
function Person(name) {
    this.name = name;
    this.greet = function () {
        console.log(`Hello, I'm ${this.name}`);
    };
}
const p1 = new Person("Alice");
const p2 = new Person("Bob");
console.log(p1.greet === p2.greet); // false (浪费内存)✅ 解决方案: 使用原型方法
            
            
              javascript
              
              
            
          
          function Person(name) {
    this.name = name;
}
Person.prototype.greet = function () {
    console.log(`Hello, I'm ${this.name}`);
};
const p1 = new Person("Alice");
const p2 = new Person("Bob");
console.log(p1.greet === p2.greet); // true (共享方法)