下面就来详细讲解一下如何用原型链的方式实现JS继承。
1. 什么是原型链
在JavaScript中,万物皆对象,每个对象都有 __proto__
属性,指向了它的原型对象。原型对象也是一个对象,也有 __proto__
属性,指向了它的原型对象。这样的对象构成了一个链状结构,被称为原型链。
2. 如何用原型链实现JS继承
原理很简单,就是在子类的原型对象上添加父类的实例作为子类的原型对象。
下面是一个例子,首先定义一个父类 Animal
:
function Animal(name) {
this.name = name;
}
Animal.prototype.say = function() {
console.log(`我的名字是${this.name}`);
}
再定义一个子类 Dog
,并将其原型对象指向 Animal
的实例:
function Dog(name, age) {
Animal.call(this, name);
this.age = age;
}
Dog.prototype = new Animal();
Dog.prototype.bark = function() {
console.log(`我的名字是${this.name},我${this.age}岁了`);
}
在 Dog
构造函数中,调用 Animal
构造函数,使 Dog
实例具有 Animal
实例的属性。然后将 Dog
的原型对象指向 Animal
的实例,使 Dog
实例可以继承 Animal
实例的方法。
现在我们创建一个 Dog
的实例:
const dog = new Dog("旺财", 3);
然后调用 Dog
实例的 bark
和 say
方法:
dog.bark(); // 我的名字是旺财,我3岁了
dog.say(); // 我的名字是旺财
这就是原型链实现 JS 继承的基本过程。
3. 原型链继承存在的问题
使用原型链继承虽然简单,但也存在一些问题:
- 所有子类实例共享父类实例,无法使用父类传参
- 对父类实例的属性修改会反映到所有子类实例上
- 无法实现多继承
下面是一个示例,说明原型链继承存在上述问题:
function Person(name) {
this.name = name;
this.hobbies = [];
}
Person.prototype.say = function() {
console.log(`我的名字是${this.name}`);
}
function Student(name, grade) {
this.grade = grade;
}
Student.prototype = new Person();
const stu1 = new Student("小明", 3);
const stu2 = new Student("小红", 2);
stu1.hobbies.push("读书");
stu2.hobbies.push("画画");
console.log(stu1.hobbies); // 输出 [ '读书', '画画' ]
console.log(stu2.hobbies); // 输出 [ '读书', '画画' ]
由于 Person
类的原型实例被多个 Student
类实例共享,所以它们的 hobbies
属性会相互影响。
4. 如何解决原型链继承的问题
解决原型链继承的问题,可以有以下几种方法:
4.1 借用构造函数
使用 call 或 apply 方法,在子类构造函数中调用父类构造函数,这样就可以在子类实例上创建一个独立的属性副本。
function Animal(name) {
this.name = name;
}
function Dog(name, age) {
Animal.call(this, name);
this.age = age;
}
const dog = new Dog("旺财", 3);
console.log(dog.name); // 输出 旺财
console.log(dog.age); // 输出 3
通过 call(this, ...)
将父类构造函数绑定到子类实例上,就可以在子类实例上创建一个独立的属性副本。
4.2 组合继承
组合继承就是将原型链继承和借用构造函数组合起来使用。首先使用原型链继承父类的原型对象,然后在子类构造函数中使用 call 或 apply 方法调用父类构造函数,以创建子类实例上的独立属性。
function Animal(name) {
this.name = name;
}
Animal.prototype.say = function() {
console.log(`我的名字是${this.name}`);
}
function Dog(name, age) {
Animal.call(this, name);
this.age = age;
}
Dog.prototype = new Animal();
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
console.log(`我的名字是${this.name},我${this.age}岁了`);
}
const dog = new Dog("旺财", 3);
console.log(dog.name); // 输出 旺财
console.log(dog.age); // 输出 3
dog.say(); // 输出 我的名字是旺财
dog.bark(); // 输出 我的名字是旺财,我3岁了
Dog.prototype = new Animal()
和 Animal.call(this, name)
这两行组合起来就实现了组合继承。首先使用原型链继承父类的原型对象,然后在子类构造函数中调用父类构造函数,以创建子类实例上的独立属性。
4.3 原型式继承
使用 Object.create(proto) 方法,创建一个空对象,并将其原型对象指向一个对象。这样这个空对象就可以继承这个对象的方法和属性。
const animal = {
say() {
console.log(`我的名字是${this.name}`);
}
}
const dog = Object.create(animal);
dog.name = "旺财";
dog.say(); // 输出 我的名字是旺财
在这个例子中 dog
对象的原型对象是 animal
对象,所以它可以调用 animal
对象的方法。
4.4 寄生式继承
寄生式继承其实是对原型式继承的一种封装。实现方法与原型式继承类似,不同的是需要在构造函数中返回一个实现了特定方法的对象。
function createDog(animal, name, age) {
const dog = Object.create(animal);
dog.name = name;
dog.age = age;
dog.bark = function() {
console.log(`我的名字是${this.name},我${this.age}岁了`);
}
return dog;
}
const animal = {
say() {
console.log(`我的名字是${this.name}`);
}
}
const dog = createDog(animal, "旺财", 3);
dog.say(); // 输出 我的名字是旺财
dog.bark(); // 输出 我的名字是旺财,我3岁了
在 createDog
函数中,使用 Object.create 方法创建一个空对象,并继承了 animal
对象的方法和属性,然后在这个空对象中添加独有的属性和方法,最后返回这个对象,以实现一种新的继承方式。
5. 总结
以上就是使用原型链实现 JS 继承的攻略,总的来说原型链继承是 JS 中最基本的继承方式之一,而组合继承则是最常用的继承方式之一。在实际开发中可以根据需求选择合适的继承方式,也可以结合多种方式来实现。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:一文详解如何用原型链的方式实现JS继承 - Python技术站