MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

JavaScript构造函数与实例方法的区别

2021-03-022.4k 阅读

JavaScript构造函数与实例方法的区别

在JavaScript的编程世界里,构造函数和实例方法是两个非常重要且容易混淆的概念。理解它们之间的区别对于深入掌握JavaScript的面向对象编程特性至关重要。接下来,我们将详细剖析这两者之间的区别,并通过丰富的代码示例来加深理解。

构造函数

  1. 定义与作用 构造函数在JavaScript中是一种特殊的函数,其主要目的是用于创建对象的实例。当使用new关键字调用构造函数时,会创建一个新的对象实例,该实例会继承构造函数的prototype对象上的属性和方法。构造函数的命名通常遵循大写字母开头的驼峰命名法,以便与普通函数区分开来。例如:
function Person(name, age) {
    this.name = name;
    this.age = age;
}

在上述代码中,Person就是一个构造函数。它接受两个参数nameage,并通过this关键字将这两个参数赋值为新创建对象的属性。

  1. 使用new关键字调用构造函数 当我们使用new关键字调用构造函数时,会发生以下几个步骤:
    • 创建一个新的空对象。
    • 将新对象的__proto__属性指向构造函数的prototype对象。
    • 将构造函数内部的this指向新创建的对象。
    • 执行构造函数内部的代码,为新对象添加属性和方法。
    • 如果构造函数没有返回其他对象,则返回新创建的对象。
let person1 = new Person('Alice', 30);
console.log(person1.name); // 输出: Alice
console.log(person1.age); // 输出: 30

在这段代码中,通过new Person('Alice', 30)创建了一个person1对象实例。person1拥有nameage属性,这是因为在调用构造函数时,this.namethis.age分别被赋值为'Alice'30

  1. 构造函数内部的this 构造函数内部的this关键字非常关键。它始终指向通过new关键字创建的新对象实例。例如:
function Animal(species) {
    this.species = species;
    console.log(this);
}
let dog = new Animal('Dog'); 
// 输出: Animal {species: 'Dog'},这里的this指向新创建的dog对象

在这个例子中,当执行new Animal('Dog')时,构造函数Animal内部的this指向新创建的dog对象,因此dog对象拥有了species属性。

  1. 构造函数的属性和方法 构造函数本身可以拥有自己的属性和方法。这些属性和方法是属于构造函数这个函数对象本身的,而不是通过构造函数创建的对象实例。例如:
function Car(make, model) {
    this.make = make;
    this.model = model;
}
Car.totalCars = 0;
Car.prototype.getDetails = function() {
    return `Make: ${this.make}, Model: ${this.model}`;
};
Car.createCar = function(make, model) {
    Car.totalCars++;
    return new Car(make, model);
};
let car1 = Car.createCar('Toyota', 'Corolla');
console.log(Car.totalCars); // 输出: 1
console.log(car1.getDetails()); // 输出: Make: Toyota, Model: Corolla

在上述代码中,totalCars是构造函数Car的静态属性,createCar是构造函数Car的静态方法。getDetails是定义在Car.prototype上的实例方法,会被所有通过Car构造函数创建的对象实例所共享。

实例方法

  1. 定义与作用 实例方法是定义在构造函数的prototype对象上的方法。当通过构造函数创建对象实例时,这些实例会继承prototype对象上的方法。实例方法可以访问实例对象的属性,并且每个实例在调用这些方法时,this关键字指向调用该方法的实例对象本身。例如:
function Circle(radius) {
    this.radius = radius;
}
Circle.prototype.getArea = function() {
    return Math.PI * this.radius * this.radius;
};
let circle1 = new Circle(5);
console.log(circle1.getArea()); // 输出: 78.53981633974483

在这段代码中,getArea是定义在Circle.prototype上的实例方法。circle1是通过Circle构造函数创建的对象实例,它可以调用getArea方法来计算自身的面积。

  1. 实例方法与this 在实例方法内部,this关键字指向调用该方法的对象实例。这使得实例方法可以访问和操作实例对象的属性。例如:
function Rectangle(width, height) {
    this.width = width;
    this.height = height;
}
Rectangle.prototype.getPerimeter = function() {
    return 2 * (this.width + this.height);
};
let rectangle1 = new Rectangle(4, 6);
console.log(rectangle1.getPerimeter()); // 输出: 20

getPerimeter方法中,this.widththis.height分别指向rectangle1对象的widthheight属性,从而可以正确计算矩形的周长。

  1. 实例方法的共享特性 由于实例方法定义在prototype对象上,所有通过同一个构造函数创建的对象实例都会共享这些方法。这意味着在内存中,这些实例方法只存在一份,而不是每个实例都有自己独立的副本。例如:
function Book(title, author) {
    this.title = title;
    this.author = author;
}
Book.prototype.getInfo = function() {
    return `Title: ${this.title}, Author: ${this.author}`;
};
let book1 = new Book('JavaScript Basics', 'John Doe');
let book2 = new Book('Advanced JavaScript', 'Jane Smith');
console.log(book1.getInfo === book2.getInfo); // 输出: true

在这个例子中,book1book2都通过Book构造函数创建,它们共享Book.prototype.getInfo方法,所以book1.getInfo === book2.getInfo返回true

  1. 动态添加实例方法 在JavaScript中,我们可以在运行时动态地向构造函数的prototype对象添加实例方法。这对于在代码执行过程中扩展对象的功能非常有用。例如:
function Triangle(side1, side2, side3) {
    this.side1 = side1;
    this.side2 = side2;
    this.side3 = side3;
}
// 动态添加实例方法
Triangle.prototype.getSideLengths = function() {
    return `Side 1: ${this.side1}, Side 2: ${this.side2}, Side 3: ${this.side3}`;
};
let triangle1 = new Triangle(3, 4, 5);
console.log(triangle1.getSideLengths()); // 输出: Side 1: 3, Side 2: 4, Side 3: 5

在上述代码中,先定义了Triangle构造函数,然后在之后动态地为其prototype对象添加了getSideLengths实例方法,新创建的triangle1对象可以立即使用这个方法。

构造函数与实例方法的区别总结

  1. 定义位置 构造函数的属性和方法通常直接定义在函数内部(如果是静态属性和方法则直接定义在构造函数本身),而实例方法定义在构造函数的prototype对象上。例如:
function User(username) {
    this.username = username;
    // 构造函数内部定义的属性
    User.staticProperty = 'Some value'; 
    // 构造函数内部定义静态属性
}
User.getStaticProperty = function() {
    return User.staticProperty;
};
// 构造函数定义静态方法
User.prototype.getUsername = function() {
    return this.username;
};
// 定义在prototype上的实例方法
let user1 = new User('user123');
console.log(user1.getUsername()); // 输出: user123
console.log(User.getStaticProperty()); // 输出: Some value
  1. 内存占用 构造函数内部定义的属性是每个对象实例都有自己的一份副本,而实例方法由于定义在prototype对象上,所有对象实例共享这些方法,在内存中只占用一份空间。例如:
function Product(name, price) {
    this.name = name;
    this.price = price;
    // 每个实例都有自己的name和price副本
}
Product.prototype.getDetails = function() {
    return `Name: ${this.name}, Price: ${this.price}`;
};
// 所有实例共享getDetails方法
let product1 = new Product('Laptop', 1000);
let product2 = new Product('Mouse', 50);

在这个例子中,product1product2都有自己独立的nameprice属性副本,但它们共享getDetails实例方法。

  1. 访问方式 构造函数的静态属性和方法通过构造函数本身来访问,而实例方法通过对象实例来访问。例如:
function Animal(species) {
    this.species = species;
}
Animal.favoriteFood = 'Generic food';
Animal.getFavoriteFood = function() {
    return Animal.favoriteFood;
};
Animal.prototype.getSpecies = function() {
    return this.species;
};
let cat = new Animal('Cat');
console.log(Animal.getFavoriteFood()); // 输出: Generic food
console.log(cat.getSpecies()); // 输出: Cat
  1. this指向 在构造函数内部,this指向新创建的对象实例。而在实例方法内部,this指向调用该方法的对象实例。例如:
function Person(name) {
    this.name = name;
    console.log(this);
    // 这里的this指向新创建的对象实例
}
Person.prototype.sayHello = function() {
    console.log(`Hello, I'm ${this.name}`);
    // 这里的this指向调用sayHello方法的对象实例
};
let person1 = new Person('Bob');
person1.sayHello();

在构造函数Person中,this在创建person1实例时指向person1。在sayHello实例方法中,this也指向person1,所以可以正确输出Hello, I'm Bob

  1. 继承关系 通过构造函数创建的对象实例会继承构造函数prototype对象上的属性和方法,包括实例方法。而构造函数本身的静态属性和方法不会被对象实例直接继承。例如:
function Shape() {
    this.type = 'Generic shape';
}
Shape.prototype.getDescription = function() {
    return `This is a ${this.type}`;
};
function Rectangle(width, height) {
    Shape.call(this);
    this.width = width;
    this.height = height;
}
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;
let rectangle1 = new Rectangle(4, 6);
console.log(rectangle1.getDescription()); // 输出: This is a Generic shape

在这个继承的例子中,Rectangle构造函数通过Object.create(Shape.prototype)继承了Shape.prototype上的getDescription实例方法,所以rectangle1可以调用该方法。但Shape构造函数的静态属性和方法不会被rectangle1继承。

  1. 适用场景 构造函数主要用于初始化对象实例的属性,以及定义一些与对象实例创建相关的逻辑。静态属性和方法适用于与整个类相关的信息和操作,比如计数器、通用的工具方法等。而实例方法适用于那些需要访问和操作对象实例属性的行为,每个实例可能有不同的属性值,所以实例方法会根据不同的实例对象产生不同的结果。例如:
function BankAccount(accountNumber, balance) {
    this.accountNumber = accountNumber;
    this.balance = balance;
    BankAccount.totalAccounts++;
}
BankAccount.totalAccounts = 0;
BankAccount.getTotalAccounts = function() {
    return BankAccount.totalAccounts;
};
BankAccount.prototype.deposit = function(amount) {
    this.balance += amount;
    return this.balance;
};
BankAccount.prototype.withdraw = function(amount) {
    if (this.balance >= amount) {
        this.balance -= amount;
        return this.balance;
    } else {
        return 'Insufficient funds';
    }
};
let account1 = new BankAccount('123456', 1000);
let account2 = new BankAccount('789012', 500);
console.log(BankAccount.getTotalAccounts()); // 输出: 2
console.log(account1.deposit(500)); // 输出: 1500
console.log(account2.withdraw(300)); // 输出: 200

在这个银行账户的例子中,构造函数BankAccount用于初始化账户的账号和余额,并更新总账户数。静态方法getTotalAccounts用于获取总账户数。实例方法depositwithdraw用于操作每个账户实例的余额。

通过深入理解构造函数和实例方法的区别,开发者可以更有效地运用JavaScript的面向对象编程特性,编写出更加健壮、高效且易于维护的代码。无论是创建简单的对象模型,还是构建复杂的大型应用程序,准确把握这两者的特性都至关重要。在实际编程中,根据具体的需求和场景,合理地使用构造函数、静态属性和方法以及实例方法,能够优化代码结构,提高代码的可读性和可扩展性。同时,对于JavaScript中基于原型的继承机制的理解,也与构造函数和实例方法的概念紧密相关,进一步掌握这些知识将有助于开发者在JavaScript编程领域不断进阶。