JS基础熟记-原型与原型链

239 阅读9分钟

JavaScript是一门基于原型的面向对象语言,其核心思想是继承和原型链。在JS中,每个对象都有一个原型对象,它扮演着类的角色,而实例则扮演着对象的角色。这种基于原型的继承机制,不同于传统的基于类的继承机制,它让JS变得更加灵活和强大。在JS中,原型和原型链是非常重要的概念,对于理解JS面向对象编程、实现继承等方面都有很大的帮助。

一、原型

每个JS对象都有一个原型对象,在创建一个对象时,JS会自动为该对象关联一个原型。在ES5之前,我们通常将原型对象称为proto,因为没有实际的方法可以获取到对象的原型。但是在ES5中,我们可以通过Object.getPrototypeOf(obj)方法来获取对象的原型。

1.1 原型的概念

原型是一个对象,其他对象可以通过它来实现属性和方法的共享。一个对象的原型也是一个对象,如果它的原型不为空,则它就可以继承来自它原型的属性和方法。

1.2 原型的构造

在JS中,每个函数都有一个prototype属性,它代表了该函数的原型。当我们使用new运算符来创建一个新对象时,实际上就是将这个对象的原型指向了函数的prototype属性。例如:

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.sayHello = function() {
  console.log('Hello, my name is ' + this.name);
}

var tom = new Person('Tom', 28);
tom.sayHello(); // Hello, my name is Tom

在上面的例子中,我们定义了一个Person函数,并在它的原型上加上了sayHello方法。当我们用new运算符创建对象时,JS会自动为新对象分配一个原型,并将该原型指向Person函数的prototype属性。因此,新对象就可以继承Person原型上的sayHello方法。

1.3 原型的应用

原型的主要作用是实现继承。在JS中,我们可以通过原型链来实现继承。如果一个对象的原型不为空,则它就可以继承来自它原型的属性和方法。例如:

function Animal() {}

Animal.prototype.eat = function() {
  console.log('Animal is eating');
}

function Dog() {}

Dog.prototype = Object.create(Animal.prototype); // 继承Animal原型

Dog.prototype.bark = function() {
  console.log('Dog is barking');
}

var snoop = new Dog();
snoop.eat(); // Animal is eating
snoop.bark(); // Dog is barking

在上面的例子中,我们定义了一个Animal函数,其中定义了一个eat方法。然后我们定义了一个Dog函数,它的原型指向了Animal的原型。这样一来,Dog就可以继承Animal的原型上的属性和方法了。我们在Dog的原型上添加了bark方法,这样snoop对象就可以调用它们了。

二、原型链

原型链是由每个对象的原型隐式指向上一个原型,从而形成的链式结构。如果一个对象需要访问一个属性或方法,但该属性或方法找不到,JS会自动沿着原型链向上查找,直到找到为止。因此,原型链也是实现继承的另一个重要机制。

2.1 原型链的概念

在JS中,原型链由一系列原型对象组成。每个对象都有一个原型对象,而原型对象又有自己的原型对象。当我们调用一个对象的属性或方法时,JS会先在该对象本身查找,如果没有找到,就会沿着原型链向上查找,直到找到为止。如果整个原型链都没有找到该属性或方法,JS会返回undefined。

2.2 原型链的构造

在JS中,我们可以通过将一个构造函数的原型对象指向另一个对象的原型,来构造原型链。例如:

function Animal() {}

Animal.prototype.eat = function() {
  console.log('Animal is eating');
}

function Dog() {}

Dog.prototype = Object.create(Animal.prototype); // 继承Animal原型

Dog.prototype.bark = function() {
  console.log('Dog is barking');
}

var snoop = new Dog();
console.log(snoop.__proto__); // Dog.prototype
console.log(snoop.__proto__.__proto__); // Animal.prototype
console.log(snoop.__proto__.__proto__.__proto__); // Object.prototype

在上面的例子中,我们定义了Animal和Dog两个函数。然后将Dog的原型指向了Animal的原型。这样一来,snoop对象就可以沿着原型链继承Animal原型上的eat方法。

2.3 原型链的应用

原型链的主要应用就是实现继承。在JS中,通过将一个构造函数的原型对象指向另一个对象的原型,我们就可以构造出一个原型链,从而实现继承。例如:

function Animal(name) {
  this.name = name;
}

Animal.prototype.eat = function() {
  console.log(this.name + ' is eating');
}

function Dog(name) {
  Animal.call(this, name); // 调用父类构造函数
}

Dog.prototype = Object.create(Animal.prototype); // 继承Animal原型

Dog.prototype.bark = function() {
  console.log(this.name + ' is barking');
}

var snoop = new Dog('Snoop');
snoop.eat(); // Snoop is eating
snoop.bark(); // Snoop is barking

在上面的例子中,我们定义了Animal和Dog两个函数。在Dog的构造函数内部,我们调用了Animal的构造函数,并传入了当前对象的this和name参数。这样一来,Dog就可以继承Animal的属性和方法了。我们通过将Dog的原型指向Animal的原型,来实现对Animal原型上eat方法的继承。最后,我们定义了Dog自己的bark方法,在snoop对象上即可调用到。

在JS中,原型和原型链是非常重要的概念。它们可以让JS变得更加灵活和强大,使得继承和多态等面向对象编程的特性都可以得到很好的实现。熟练掌握原型和原型链,可以让我们更好地理解JS的面向对象机制,写出更加优雅、高效的代码。

附:完整代码

function Animal(name) {
  this.name = name;
}

Animal.prototype.eat = function() {
  console.log(this.name + ' is eating');
}

function Dog(name) {
  Animal.call(this, name); // 调用父类构造函数
}

Dog.prototype = Object.create(Animal.prototype); // 继承Animal原型

Dog.prototype.bark = function() {
  console.log(this.name + ' is barking');
}

var snoop = new Dog('Snoop');
snoop.eat(); // Snoop is eating
snoop.bark(); // Snoop is barking

console.log(snoop.__proto__); // Dog.prototype
console.log(snoop.__proto__.__proto__); // Animal.prototype
console.log(snoop.__proto__.__proto__.__proto__); // Object.prototype

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.sayHello = function() {
  console.log('Hello, my name is ' + this.name);
}

var tom = new Person('Tom', 28);
tom.sayHello(); // Hello, my name is Tom

顺道讲解一下构造函数和类的概念,打牢基础,这对于理解 JavaScript 的面向对象编程非常重要。

三、构造函数

在 JS 中,构造函数是一种特殊类型的函数,它们用于创建对象。与普通函数不同的是,构造函数通常首字母大写,而且必须使用 new 运算符来调用。当我们用 new 运算符来调用一个构造函数时,JS 会自动为我们创建一个对象,并将该对象的原型指向构造函数的原型。

3.1 构造函数的概念

构造函数是一种特殊类型的函数,它们用于创建对象。与普通函数不同的是,构造函数通常首字母大写,而且必须使用 new 运算符来调用。构造函数内部通常会定义一些属性和方法,这些属性和方法会在每个通过构造函数创建的对象中共享。

function Person(name, age) {
  this.name = name;
  this.age = age;
}

var tom = new Person('Tom', 28);
console.log(tom.name); // Tom
console.log(tom.age); // 28

在上面的例子中,我们定义了一个名为 Person 的构造函数,它接受两个参数 name 和 age,然后将它们保存到新创建的对象中。我们使用 new 运算符来调用该构造函数,并将返回值保存到变量 tom 中。此时,tom 就是一个通过 Person 构造函数创建的对象。

3.2 构造函数的应用

构造函数最常见的应用就是创建对象。通过定义一个构造函数,并使用它来创建对象,我们可以轻松地创建多个具有相同属性和方法的对象。例如,我们可以定义一个 Car 构造函数,并用它来创建多个汽车对象:

function Car(brand, model, price) {
  this.brand = brand;
  this.model = model;
  this.price = price;
}

var car1 = new Car('Toyota', 'Camry', 25000);
var car2 = new Car('Honda', 'Accord', 28000);
console.log(car1.brand); // Toyota
console.log(car2.model); // Accord

在上面的例子中,我们定义了一个名为 Car 的构造函数,每个对象都会有 brand、model 和 price 属性。然后我们使用该构造函数来创建两个不同的汽车对象 car1 和 car2。

四、类

在 ES6 中,JS 引入了 class 关键字,使得 JS 变得更加接近传统的面向对象语言。使用 class 关键字可以轻松地定义一个类,并在该类中定义属性和方法。类也可以继承其他类,并重写父类的方法。将 class 与 constructor 和 prototype 结合使用,我们就可以完整地实现面向对象编程。

4.1 类的概念

在 JS 中,类是一种特殊类型的对象,它通常由属性和方法组成。类可以看作是一种模板或蓝图,它描述了对象应该具有的属性和方法。在我们创建一个类的实例时,JS 会根据该类的定义来为我们创建一个对象,并将其作为实例返回。

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  
  sayHello() {
    console.log('Hello, my name is ' + this.name);
  }
}

let tom = new Person('Tom', 28);
tom.sayHello(); // Hello, my name is Tom

在上面的例子中,我们使用 class 关键字定义了一个名为 Person 的类。Person 类有一个 constructor 方法,用于在创建新对象时初始化对象的属性。Person 类还有一个名为 sayHello 的方法,可以输出一个问候语。

然后我们使用 new 运算符来创建一个 Person 类的实例,并将其保存到 tom 变量中。此时,tom 就是一个 Person 类的实例。

4.2 类的应用

类最常见的应用就是创建对象。通过定义一个类,并使用它来创建对象,我们可以轻松地创建多个具有相同属性和方法的对象。例如,我们可以定义一个 Car 类,并用它来创建多个汽车对象:

class Car {
  constructor(brand, model, price) {
    this.brand = brand;
    this.model = model;
    this.price = price;
  }
  
  getFullName() {
    return this.brand + ' ' + this.model;
  }
}

let car1 = new Car('Toyota', 'Camry', 25000);
let car2 = new Car('Honda', 'Accord', 28000);
console.log(car1.getFullName()); // Toyota Camry
console.log(car2.getFullName()); // Honda Accord

在上面的例子中,我们定义了一个名为 Car 的类,它有三个属性 brand、model 和 price,以及一个 getFullName 方法。最后,我们使用该类来创建两个不同的汽车对象 car1 和 car2,并调用它们的 getFullName 方法。

总结

在 JS 中,原型和原型链是实现继承的重要机制,而构造函数则是创建对象的重要手段。在 ES6 中,JS 引入了 class 关键字,使得 JS 变得更加接近传统的面向对象语言。熟练掌握这些概念和关键字,可以让我们更好地理解 JS 的面向对象编程,写出更加优雅、高效的代码。