🔑 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影响)
这个扩展示例展示了:
-
多层原型链:
specificDog→dog→animal→Object.prototype→null -
属性查找顺序:
- 首先在对象自身查找属性
- 然后沿着原型链向上查找
- 一旦找到属性就停止查找并使用该值
-
方法中的
this:- 方法中的
this始终指向调用该方法的对象,而不是定义该方法的对象 - 这就是为什么
specificDog.eat()中的this.name是"旺财"
- 方法中的
-
修改原型:
- 修改原型对象会影响所有继承该原型的对象
- 但如果对象自身已定义同名属性,则不受影响
-
属性检查:
hasOwnProperty方法可以检查属性是直接定义在对象上还是继承自原型
与多态(Polymorphism)的关系
原型链是JavaScript特有的一种机制,它用于对象之间的继承。通过原型链,一个对象可以访问另一个对象的属性和方法。这是实现对象间继承和方法复用的一种方式。
多态是面向对象编程中的一个概念,指的是同一操作作用于不同的对象上时,可以有不同的行为。JavaScript通过原型链和构造函数(以及ES6中的类)可以实现多态。子类对象可以覆盖继承自父类的方法,以实现不同的行为。这种方法在JavaScript中是通过原型链实现的。
在JavaScript中,多态是通过原型链实现的。子类可以继承父类的方法和属性,子类的实例可以根据需要覆盖这些方法和属性以实现多态性。