一句话通俗解释JavaScript系列 - 原型链

70 阅读4分钟

🔑 JavaScript的原型链是一种使得对象能够继承其他对象属性和方法的机制,好比一个家族的遗传链,如果孩子没有某个特征,就会向上追溯到父母或祖辈中去找。

以Todo List为例:

在开发一个Todo List应用时,原型链的概念可以用于多种场景,尤其是在创建对象和继承共有方法时。这里有几个具体的实际用途示例:

1. 继承共有方法

假设你有多种类型的任务,比如普通任务紧急任务长期任务。它们都有一些共同的方法,比如完成任务显示详情等,但是也有一些特殊的方法或属性。你可以创建一个Task基类,它包含所有任务共有的方法和属性。然后,你可以创建普通任务紧急任务长期任务这些子类,它们通过原型链继承Task基类的方法和属性,同时也可以添加或重写自己特有的方法和属性。

2. 提高性能和内存效率

通过使用原型链,所有从同一原型继承的实例共享相同的方法,而不是在每个实例中复制这些方法。这意味着如果你有成千上万的任务实例,它们都会使用相同的方法引用而不是每个实例都有自己的副本,从而节省内存。

3. 动态修改共有行为

通过原型链,你可以在运行时动态地修改原型上的方法,这些改变会立即反映到所有从该原型继承的实例上。比如,如果你想为所有任务添加一个新的共有方法延期,你只需在Task原型上添加这个方法,所有的任务类型都会立即获得这个新方法。

示例代码

// 基类
function Task(name) {
    this.name = name;
    this.isCompleted = false;
}

Task.prototype.complete = function() {
    this.isCompleted = true;
    console.log(this.name + "任务完成!");
};

Task.prototype.display = function() {
    console.log(this.name + " - 完成状态:" + this.isCompleted);
};

// 继承Task创建特定任务类型
function UrgentTask(name, priority) {
    Task.call(this, name); // 调用父类构造函数
    this.priority = priority;
}

// 设置原型链实现继承
UrgentTask.prototype = Object.create(Task.prototype);
UrgentTask.prototype.constructor = UrgentTask;

// 为UrgentTask添加特有方法
UrgentTask.prototype.display = function() {
    console.log(this.name + " - 优先级:" + this.priority + " - 完成状态:" + this.isCompleted);
};

// 使用
var myTask = new Task("普通任务");
var myUrgentTask = new UrgentTask("紧急任务", "高");

myTask.complete();
myTask.display();

myUrgentTask.complete();
myUrgentTask.display();

在这个例子中,通过使用原型链,UrgentTask继承了Task的方法,同时添加了自己的属性priority和重写了display方法以显示优先级。

再给个例子

// 创建基础原型对象
let animal = {
  energy: 100,
  makeSound: function() {
    return "某种声音";
  },
  eat: function() {
    this.energy += 10;
    return `${this.name}吃东西了,能量增加到${this.energy}`;
  },
  sleep: function() {
    this.energy += 20;
    return `${this.name}睡觉了,能量增加到${this.energy}`;
  }
};

// 创建继承自animal的dog对象
let dog = Object.create(animal);
dog.name = "小狗";
dog.breed = "哈士奇";
dog.makeSound = function() {
  this.energy -= 5;
  return `汪汪!(能量减少到${this.energy})`;
};

// 创建继承自dog的specificDog对象
let specificDog = Object.create(dog);
specificDog.name = "旺财";
specificDog.age = 3;

// 让我们看看原型链如何工作
console.log(specificDog.name);         // "旺财" (在specificDog上找到)
console.log(specificDog.breed);        // "哈士奇" (在dog上找到)
console.log(specificDog.energy);       // 100 (在animal上找到)

// 调用方法,展示原型链上方法的调用
console.log(specificDog.makeSound());  // "汪汪!(能量减少到95)" (在dog上找到)
console.log(specificDog.eat());        // "旺财吃东西了,能量增加到105" (在animal上找到)

// 添加一个原型不存在的方法到Object.prototype
Object.prototype.introduce = function() {
  return `我是${this.name},我是一个${this.constructor.name}对象`;
};

// 现在所有对象都可以访问这个方法
console.log(specificDog.introduce());  // "我是旺财,我是一个Object对象"

// 检查原型链
console.log(Object.getPrototypeOf(specificDog) === dog);          // true
console.log(Object.getPrototypeOf(dog) === animal);               // true
console.log(Object.getPrototypeOf(animal) === Object.prototype);  // true
console.log(Object.getPrototypeOf(Object.prototype) === null);    // true

// 检查属性存在于哪里
console.log(specificDog.hasOwnProperty('name'));    // true (直接在specificDog上)
console.log(specificDog.hasOwnProperty('breed'));   // false (在原型dog上)
console.log(specificDog.hasOwnProperty('energy'));  // false (在原型链更高处的animal上)
console.log(specificDog.hasOwnProperty('toString')); // false (在Object.prototype上)

// 修改原型对象会影响所有继承的对象
animal.energy = 200;
console.log(specificDog.energy);  // 200 (影响了整个原型链)

// 但是如果specificDog自己有了energy属性,就不会受影响
specificDog.energy = 150;
animal.energy = 300;
console.log(specificDog.energy);  // 150 (自己的属性优先)
console.log(dog.energy);          // 300 (仍然受animal影响)

这个扩展示例展示了:

  1. 多层原型链: specificDogdoganimalObject.prototypenull

  2. 属性查找顺序:

    • 首先在对象自身查找属性
    • 然后沿着原型链向上查找
    • 一旦找到属性就停止查找并使用该值
  3. 方法中的this:

    • 方法中的this始终指向调用该方法的对象,而不是定义该方法的对象
    • 这就是为什么specificDog.eat()中的this.name是"旺财"
  4. 修改原型:

    • 修改原型对象会影响所有继承该原型的对象
    • 但如果对象自身已定义同名属性,则不受影响
  5. 属性检查:

    • hasOwnProperty方法可以检查属性是直接定义在对象上还是继承自原型

与多态(Polymorphism)的关系

原型链是JavaScript特有的一种机制,它用于对象之间的继承。通过原型链,一个对象可以访问另一个对象的属性和方法。这是实现对象间继承和方法复用的一种方式。

多态是面向对象编程中的一个概念,指的是同一操作作用于不同的对象上时,可以有不同的行为。JavaScript通过原型链和构造函数(以及ES6中的类)可以实现多态。子类对象可以覆盖继承自父类的方法,以实现不同的行为。这种方法在JavaScript中是通过原型链实现的。

在JavaScript中,多态是通过原型链实现的。子类可以继承父类的方法和属性,子类的实例可以根据需要覆盖这些方法和属性以实现多态性。