Skip to content

当我们在 JavaScript 中创建对象时,通常会使用 new 关键字调用一个函数。这个被 new 调用的函数就是我们所说的构造函数。但你有没有想过,在这个过程中,this 到底发生了什么奇妙的变化?

让我们先看一个简单的例子:

javascript
function Person(name, age) {
  this.name = name;
  this.age = age;
  this.sayHello = function () {
    return `Hello, I'm ${this.name}`;
  };
}

const john = new Person("John", 30);
console.log(john.name); // 'John'
console.log(john.sayHello()); // "Hello, I'm John"

在这个例子中,this 不再指向某个特定的对象,而是指向一个新创建的对象。这种转变正是构造函数的魅力所在。

构造函数的本质

构造函数本质上是一个普通的函数,它之所以成为构造函数,完全是因为 new 操作符的存在。当你使用 new 调用一个函数时,JavaScript 引擎会在背后执行几个关键步骤:

  1. 创建新对象:创建一个新的空对象
  2. 原型链接:将这个新对象的 [[Prototype]] 链接到构造函数的 prototype 属性
  3. this 绑定:将构造函数中的 this 指向这个新对象
  4. 执行构造函数:执行构造函数体内的代码
  5. 返回对象:如果构造函数没有显式返回其他对象,则返回这个新对象
javascript
function Car(brand, model) {
  // 在这一步,this 已经指向了新创建的对象
  this.brand = brand;
  this.model = model;
  this.year = 2024;

  // 这里的 this 仍然是新创建的对象
  this.getInfo = function () {
    return `${this.brand} ${this.model} (${this.year})`;
  };
}

const myCar = new Car("Toyota", "Camry");
console.log(myCar.getInfo()); // "Toyota Camry (2024)"

this 在构造函数中的特殊行为

1. 指向新创建的对象

在构造函数中,this 始终指向由 new 操作符创建的新对象。这意味着你可以在构造函数中为这个对象添加属性和方法:

javascript
function Smartphone(brand, screenSize) {
  // this 指向新创建的 Smartphone 对象
  this.brand = brand;
  this.screenSize = screenSize;
  this.isTurnedOn = false;

  this.turnOn = function () {
    this.isTurnedOn = true;
    return `${this.brand} is now turned on`;
  };

  this.turnOff = function () {
    this.isTurnedOn = false;
    return `${this.brand} is now turned off`;
  };
}

const phone = new Smartphone("iPhone", "6.1 inches");
console.log(phone.turnOn()); // "iPhone is now turned on"
console.log(phone.isTurnedOn); // true

2. 构造函数中的嵌套函数

需要注意的是,构造函数中定义的嵌套函数中的 this 不再指向新创建的对象。这是一个常见的陷阱:

javascript
function Account(balance) {
  this.balance = balance;
  this.transactions = [];

  // 这个嵌套函数中的 this 不指向 Account 实例
  function addTransaction(amount) {
    // 这里的 this 可能指向 window (非严格模式) 或 undefined (严格模式)
    this.balance += amount; // 错误!
  }

  // 正确的做法:
  const self = this;
  this.addTransaction = function (amount) {
    self.balance += amount; // 使用闭包保存 this
  };
}

const account = new Account(1000);
account.addTransaction(500);
console.log(account.balance); // 1500

构造函数的返回值处理

构造函数有一个特殊的行为:它可以有不同的返回值处理方式。

1. 默认返回新对象

如果构造函数没有显式返回值,或者返回的是基本数据类型,JavaScript 会返回新创建的对象:

javascript
function User(name) {
  this.name = name;
  // 没有显式 return,返回新创建的对象
}

const user = new User("Alice");
console.log(user.name); // 'Alice'
javascript
function Product(name, price) {
  this.name = name;
  this.price = price;
  return 42; // 返回基本类型,被忽略
}

const product = new Product("Laptop", 999);
console.log(product.price); // 999,仍然返回新对象

2. 显式返回对象

如果构造函数显式返回一个对象,那么这个返回的对象会替代新创建的对象:

javascript
function Singleton(name) {
  this.name = name;

  // 返回一个已存在的对象
  return {
    name: "Singleton Instance",
    created: new Date(),
  };
}

const instance = new Singleton("Test");
console.log(instance.name); // 'Singleton Instance',不是 'Test'
console.log(instance.created); // 存在 created 属性

这种特性可以用来实现单例模式:

javascript
function DatabaseConnection() {
  // 检查是否已存在实例
  if (DatabaseConnection.instance) {
    return DatabaseConnection.instance;
  }

  this.connected = false;
  this.connectionId = Math.random();

  DatabaseConnection.instance = this;
}

const db1 = new DatabaseConnection();
const db2 = new DatabaseConnection();

console.log(db1 === db2); // true
console.log(db1.connectionId === db2.connectionId); // true

与 ES6 类的关系

ES6 引入了 class 语法,但它本质上仍然是构造函数的语法糖:

javascript
// ES6 类语法
class Animal {
  constructor(species, age) {
    this.species = species;
    this.age = age;
  }

  makeSound() {
    return `${this.species} makes a sound`;
  }
}

// 等价于构造函数语法
function Animal(species, age) {
  this.species = species;
  this.age = age;
}

Animal.prototype.makeSound = function () {
  return `${this.species} makes a sound`;
};

class 语法中,constructor 方法里的 this 行为与普通构造函数完全一致:

javascript
class Book {
  constructor(title, author, price) {
    this.title = title;
    this.author = author;
    this.price = price;
    this.isAvailable = true;
  }

  purchase() {
    if (this.isAvailable) {
      this.isAvailable = false;
      return `You purchased ${this.title}`;
    }
    return `${this.title} is not available`;
  }
}

const book = new Book("JavaScript Guide", "John Doe", 29.99);
console.log(book.purchase()); // "You purchased JavaScript Guide"

实际应用场景

1. 数据模型创建

构造函数非常适合用来创建复杂的数据模型:

javascript
function Student(id, name, grade) {
  this.id = id;
  this.name = name;
  this.grade = grade;
  this.courses = [];

  this.enrollCourse = function (courseName) {
    this.courses.push(courseName);
    return `${this.name} enrolled in ${courseName}`;
  };

  this.getAverage = function () {
    if (this.courses.length === 0) return 0;
    // 简化的平均分计算
    return this.grade;
  };
}

const student = new Student(1, "Emma", 85);
student.enrollCourse("Math");
student.enrollCourse("Science");
console.log(student.courses); // ['Math', 'Science']

2. 组件创建

在前端开发中,构造函数常用于创建 UI 组件:

javascript
function Button(text, onClick) {
  this.text = text;
  this.onClick = onClick;
  this.element = null;

  this.render = function () {
    this.element = document.createElement("button");
    this.element.textContent = this.text;
    this.element.addEventListener("click", this.onClick);
    return this.element;
  };

  this.destroy = function () {
    if (this.element) {
      this.element.removeEventListener("click", this.onClick);
      this.element.remove();
    }
  };
}

const button = new Button("Click Me", () => {
  console.log("Button clicked!");
});

document.body.appendChild(button.render());

常见误区与解决方案

1. 忘记使用 new 操作符

javascript
function Person(name) {
  this.name = name;
}

const person1 = new Person("John"); // 正确
const person2 = Person("Jane"); // 错误!this 指向全局对象

console.log(person1.name); // 'John'
console.log(person2); // undefined
console.log(window.name); // 'Jane' (非严格模式)

解决方案:使用严格模式或添加检查:

javascript
function Person(name) {
  "use strict";
  if (!(this instanceof Person)) {
    throw new Error("必须使用 new 操作符调用构造函数");
  }
  this.name = name;
}

2. 构造函数中返回错误的对象

javascript
function BadConstructor() {
  this.property = "value";
  return { wrong: "object" }; // 这会覆盖新创建的对象
}

const bad = new BadConstructor();
console.log(bad.property); // undefined
console.log(bad.wrong); // 'object'

最佳实践

1. 构造函数命名约定

构造函数通常使用大驼峰命名法(PascalCase),以区别于普通函数:

javascript
function UserManager() {} // 构造函数
function getUserData() {} // 普通函数

2. 将方法添加到原型上

为了节省内存,将方法定义在原型上而不是在构造函数中:

javascript
// 不推荐:每次创建实例都会创建新的函数
function Circle(radius) {
  this.radius = radius;
  this.getArea = function () {
    return Math.PI * this.radius * this.radius;
  };
}

// 推荐:所有实例共享同一个方法
function Circle(radius) {
  this.radius = radius;
}

Circle.prototype.getArea = function () {
  return Math.PI * this.radius * this.radius;
};

3. 使用工厂模式替代构造函数

有时候,工厂函数比构造函数更加灵活:

javascript
// 工厂函数
function createVehicle(type, brand) {
  const vehicle = {
    type: type,
    brand: brand,
    drive() {
      return `${this.brand} ${this.type} is driving`;
    },
  };

  if (type === "car") {
    vehicle.doors = 4;
  } else if (type === "motorcycle") {
    vehicle.doors = 0;
  }

  return vehicle;
}

const car = createVehicle("car", "Toyota");
const motorcycle = createVehicle("motorcycle", "Honda");

总结

构造函数中的 this 是 JavaScript 对象创建机制的核心。理解它的行为对于掌握面向对象编程至关重要:

  • 新对象绑定this 指向 new 创建的新对象
  • 返回值处理:默认返回新对象,显式返回对象会替代新对象
  • 原型链建立:自动建立原型链接
  • 与 class 的关系:ES6 类是构造函数的语法糖

掌握了构造函数中的 this,你就掌握了 JavaScript 对象创建的魔法,可以构建出更加结构化和可维护的应用程序。