《高程》书上对继承问题这一块讲的太乱,举的例子也不好,重新整理如下以备自用。
了解即可,现在都用ES6的class来实现继承。
原型链实现的继承有什么问题?
- 对象间共享属性和方法:由于原型链上的所有对象都共享一个原型对象,因此对原型对象的修改会影响到所有继承自该原型对象的子对象,这可能导致意外的副作用。
- 构造函数无法传参:通过原型链继承父类的属性和方法时,无法像盗用构造函数那样在子类构造函数中传递参数,这限制了子类构造函数的灵活性。
- 父类的引用属性会被所有子类实例共享:如果父类中包含引用类型的属性,那么这些属性会被所有子类实例共享,这可能导致意外的修改和副作用。【高程书p242上的例子】
- 无法访问父类实例的私有属性和方法:原型链继承只能访问父类构造函数和原型对象上的公有属性和方法,而无法访问父类实例中的私有属性和方法。
- 原型对象中包含的方法会被所有实例共享,可能会影响性能:由于所有继承自同一原型对象的实例都共享同一个方法,如果这个方法的执行代价很高,那么就可能会影响到所有实例的性能。
技术1:盗用构造函数
在 JavaScript 中,盗用构造函数(Constructor Stealing)是一种继承模式,也称为伪类模式(Pseudo-classical Inheritance)或伪继承模式(Pseudo Inheritance)。
该模式的核心思想是在子类构造函数中调用父类构造函数,并将父类构造函数的上下文对象(this)绑定到子类实例对象上,以达到继承父类属性和方法的目的。
具体来说,假设有一个父类构造函数 Parent,和一个子类构造函数 Child。Child 可以通过以下方式继承 Parent 的属性和方法:
function Parent(name) {
this.name = name;
this.sayName = function() {
console.log("My name is " + this.name);
}
}
function Child(name, age) {
Parent.call(this, name); // 调用父类构造函数并传入当前子类实例对象作为上下文对象
this.age = age;
}
在上面的例子中,Child 构造函数通过调用 Parent 构造函数,并传入当前子类实例对象 this,来实现继承父类属性和方法的目的。这样一来,Child 的实例就可以访问 Parent 的属性和方法,例如:
var child = new Child("Tom", 10);
console.log(child.name); // 输出 "Tom"
child.sayName(); // 输出 "My name is Tom"
需要注意的是,盗用构造函数模式只能继承父类构造函数中定义的属性和方法,而无法继承父类原型对象中定义的属性和方法。如果需要继承父类原型对象中的属性和方法,可以通过组合继承(Combination Inheritance)等其他继承模式来实现。
技术2:组合继承
组合继承是 JavaScript 中一种常见的继承方式,它结合了原型链继承和盗用构造函数继承的优点,解决了它们各自的问题。组合继承的核心思想是使用原型链继承父类原型对象中的属性和方法,同时在子类构造函数中通过盗用构造函数继承父类构造函数中的属性和方法,从而实现继承父类所有属性和方法的目的。
具体来说,假设有一个父类构造函数 Parent,和一个子类构造函数 Child。Child 可以通过以下方式实现继承父类所有属性和方法:
function Parent(name) {
this.name = name;
this.sayName = function() {
console.log("My name is " + this.name);
}
}
function Child(name, age) {
Parent.call(this, name); // 盗用构造函数继承父类构造函数中的属性和方法
this.age = age;
}
Child.prototype = new Parent(); // 原型链继承父类原型对象中的属性和方法
Child.prototype.constructor = Child; // 修复子类构造函数指向问题
在上面的例子中,Child 构造函数首先通过盗用构造函数的方式继承了父类构造函数中的属性和方法,然后使用原型链继承父类原型对象中的属性和方法。这样一来,Child 就可以继承父类所有属性和方法.
需要注意的是,虽然组合继承解决了原型链继承和盗用构造函数继承各自的问题,但也存在一些性能问题。由于每个子类实例都包含了一份父类构造函数中的属性和方法,因此可能会浪费一些内存空间。此外,由于每次创建子类实例时都会执行一次父类构造函数,因此也可能会对性能造成一些影响。
技术3:原型式继承
原型式继承是 JavaScript 中一种比较简单的继承方式,它基于已有的对象创建一个新对象,而新对象的原型就是已有对象本身。因此,它的核心思想是使用一个函数来封装对象的创建过程,然后通过该函数来创建新对象,从而实现继承已有对象的属性和方法。
具体来说,原型式继承可以使用 Object.create() 方法来实现。这个方法接收一个参数,即新对象的原型,然后返回一个新对象,从而实现了原型式继承的目的。例如:
var parent = {
name: "Tom",
sayName: function() {
console.log("My name is " + this.name);
}
};
var child = Object.create(parent); // 使用 Object.create() 方法创建一个新对象,并将其原型设置为 parent
child.age = 10;
console.log(child.name); // 输出 "Tom"
child.sayName(); // 输出 "My name is Tom"
console.log(child.age); // 输出 10
在上面的例子中,我们首先定义了一个 parent 对象,然后使用 Object.create() 方法创建了一个新对象 child,并将其原型设置为 parent。这样一来,child 就继承了 parent 对象的所有属性和方法。需要注意的是,由于 Object.create() 方法本质上是对已有对象的浅拷贝,因此如果 parent 对象中包含引用类型的属性,那么这些属性也会被 child 对象共享,可能导致意外的修改和副作用。
虽然原型式继承是一种比较简单的继承方式,但它也存在一些问题。由于它并没有使用构造函数来创建新对象,因此无法传递参数,从而限制了子类的灵活性。此外,由于它是基于已有对象的浅拷贝实现的,因此如果已有对象中包含引用类型的属性,那么这些属性会被所有继承自该对象的子对象共享,可能导致意外的修改和副作用。因此,在实际开发中需要根据具体情况进行选择,可能需要结合其他继承模式来实现更合适的继承方式。