在 JavaScript 中,原型链和继承是对象系统的核心概念。它们通过原型机制让对象之间能够共享属性和方法,从而实现代码复用和对象的灵活扩展。
每个 JavaScript 对象在创建时都会拥有一个内部链接,指向另一个对象(称为原型对象)。这个原型对象包含了该对象可以继承的属性和方法。原型的存在使得对象可以共享一些通用的行为。
在 JavaScript 中,可以通过 __proto__
(或在 ES6 中的 Object.getPrototypeOf
和 Object.setPrototypeOf
)来访问或设置一个对象的原型。
JavaScript 中的对象通过原型链来实现继承。当我们访问一个对象的属性或方法时,如果该对象本身没有该属性或方法,JavaScript 引擎会沿着该对象的原型链向上查找,直到找到该属性或方法,或到达原型链的顶端(即 null
)为止。
原型链的工作过程如下:
undefined
。function Animal() {}
Animal.prototype.sayHello = function() {
console.log("Hello from Animal");
};
const animal = new Animal();
animal.sayHello(); // 输出 "Hello from Animal"
// 查找过程:animal -> Animal.prototype -> Object.prototype -> null
在上例中,animal
的原型是 Animal.prototype
,而 Animal.prototype
的原型是 Object.prototype
,这形成了一条原型链 animal -> Animal.prototype -> Object.prototype -> null
。
JavaScript 提供了多种实现继承的方式。以下是几种常见的方法:
在传统的构造函数继承中,我们通过在子类的构造函数中调用父类构造函数来实现继承。
function Animal(name) {
this.name = name;
}
Animal.prototype.sayHello = function() {
console.log("Hello, I am " + this.name);
};
function Dog(name, breed) {
Animal.call(this, name); // 调用父类构造函数
this.breed = breed;
}
// 设置原型链
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // 重新设置构造函数引用
const dog = new Dog("Buddy", "Golden Retriever");
dog.sayHello(); // 输出 "Hello, I am Buddy"
这里的步骤如下:
Animal.call(this, name);
在子类构造函数中调用父类构造函数,使得 name
被绑定在 Dog
实例上。Dog.prototype = Object.create(Animal.prototype);
创建 Dog.prototype
并将其链接到 Animal.prototype
,从而实现继承。Dog.prototype.constructor = Dog;
将 constructor
指向子类构造函数 Dog
,避免指向 Animal
。ES6 引入了 class
语法,使得继承的实现更加直观。它其实是对原型链和构造函数继承的一层封装,依然基于原型链。
class Animal {
constructor(name) {
this.name = name;
}
sayHello() {
console.log("Hello, I am " + this.name);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // 调用父类构造函数
this.breed = breed;
}
bark() {
console.log("Woof! Woof!");
}
}
const dog = new Dog("Buddy", "Golden Retriever");
dog.sayHello(); // 输出 "Hello, I am Buddy"
dog.bark(); // 输出 "Woof! Woof!"
在 Dog
类中,super(name);
调用了父类 Animal
的构造函数,保证了 name
被正确赋值。ES6 的继承方式更简洁,推荐在现代项目中使用。
3.3 使用 Object.create()
实现原型继承
Object.create()
可以基于一个现有对象创建新的对象,并将该对象作为新对象的原型。这种方法简单灵活,但不支持构造函数传参。
const animal = {
sayHello() {
console.log("Hello from animal");
}
};
const dog = Object.create(animal);
dog.bark = function() {
console.log("Woof! Woof!");
};
dog.sayHello(); // 输出 "Hello from animal"
dog.bark(); // 输出 "Woof! Woof!"
原型链继承虽然简单,但存在一些缺点:
我们先来看一个演示,展示原型链继承会导致实例共享引用类型属性的问题。
// 基类 Animal
function Animal(name) {
this.name = name;
this.friends = []; // 引用类型属性
}
Animal.prototype.addFriend = function(friend) {
this.friends.push(friend);
};
function Dog(name, breed) {
Animal.call(this, name); // 调用父类构造函数
this.breed = breed;
}
// 通过原型链继承 Animal
Dog.prototype = new Animal();
Dog.prototype.constructor = Dog;
// 创建 Dog 类的实例
const dog1 = new Dog("Buddy", "Golden Retriever");
const dog2 = new Dog("Max", "Bulldog");
// 向 dog1 添加朋友
dog1.addFriend("Charlie");
console.log(dog1.friends); // 输出 ["Charlie"]
console.log(dog2.friends); // 输出 ["Charlie"],因为它们共享同一个 friends 数组
原型链和继承在 JavaScript 中被广泛应用于:
HTMLElement
、Node
等都具有原型链关系,通过原型链进行方法继承。下边给一个demo 来描述使用场景
假设我们有一个动物园系统,我们需要管理不同类型的动物。所有动物有一些共通的属性和方法(如名称、叫声),但是不同种类的动物可能有不同的行为。我们可以通过继承来实现这一点。
// 基类 Animal
function Animal(name) {
this.name = name; // 动物的名字
}
Animal.prototype.makeSound = function() {
console.log(this.name + " makes a sound");
};
// 派生类 Dog,继承自 Animal
function Dog(name, breed) {
Animal.call(this, name); // 调用父类构造函数,初始化 Dog 的 name 属性
this.breed = breed; // 狗的品种
}
// 通过 Object.create 创建一个继承自 Animal.prototype 的 Dog.prototype
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // 修正构造函数的指向
// 添加 Dog 特有的方法
Dog.prototype.bark = function() {
console.log(this.name + " barks: Woof! Woof!");
};
// 派生类 Cat,继承自 Animal
function Cat(name, color) {
Animal.call(this, name); // 调用父类构造函数,初始化 Cat 的 name 属性
this.color = color; // 猫的颜色
}
// 通过 Object.create 创建一个继承自 Animal.prototype 的 Cat.prototype
Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.constructor = Cat; // 修正构造函数的指向
// 添加 Cat 特有的方法
Cat.prototype.meow = function() {
console.log(this.name + " meows: Meow! Meow!");
};
// 创建 Dog 和 Cat 的实例
const dog = new Dog("Buddy", "Golden Retriever");
const cat = new Cat("Whiskers", "Tabby");
// 使用父类和子类的方法
dog.makeSound(); // 输出 "Buddy makes a sound"
dog.bark(); // 输出 "Buddy barks: Woof! Woof!"
cat.makeSound(); // 输出 "Whiskers makes a sound"
cat.meow(); // 输出 "Whiskers meows: Meow! Meow!"
Dog
和 Cat
类都通过 Object.create(Animal.prototype)
来继承 Animal
类的方法。这种方式确保了每个实例都可以访问父类的方法,并且通过在子类中添加特有的方法,实现多态和行为扩展。dog
和 cat
)都能调用 makeSound
方法,这是从父类 Animal
继承来的。同时,Dog
和 Cat
类还各自有各自的特有方法,如 bark
和 meow
。Dog
和 Cat
类共享了 Animal
类的 makeSound
方法,避免了重复编写相同的功能。Bird
),可以继承自 Animal
类并添加特有的行为,代码维护和扩展变得非常简便。Dog
和 Cat
类继承了 makeSound
方法,但是它们仍然可以各自拥有不同的行为方法(如 bark
和 meow
),体现了多态的特性。通过原型链和继承,我们可以实现一个灵活的面向对象系统,使得不同对象共享通用功能的同时,又能根据需求拓展各自的特性。这种设计不仅提高了代码的复用性,还让程序更容易扩展和维护。
class
继承和 Object.create()
。class
语法糖。通过掌握这些知识,可以深入理解 JavaScript 的对象系统,合理地实现代码复用和扩展。