继承的几种方法
原型链
在讲继承之前,首先我们得知道什么是原型什么是原型链。这里我不做具体的讲解,默认大家知道什么是原型以及原型链,简单举个例子。(每个实例都有他自己的原型,我的理解就是原型相当于是实例的父亲,实例是原型的儿子,当相亲的时候这个儿子被问有没有房有没有车的时候,儿子如果自己没有,就问老子有没有,老子要是也没有,就找爷,爷要是爷没有就继续往上找,直到找到原始人祖先,原始人祖先再往上找就不是人类物种了,这里默认原始人祖先没有父亲,所以直接返回null,因此console.log(儿子.车,儿子.房 // undefine,undefine))
原型链就上面举的例子差不多。
儿子 => 父亲 => 爷 => ···· => 原始人祖先 => null
实例 => 实例的原型 => 实例的原型的原型 => ··· => OBJECT => null
获取原型的方式
// 实例获取原型的方式
console.log(obj.__proto__ ) // 获取实例的原型
console.log(myFunction.prototype) // 获取构造函数的原型
这里可能有人想问什么是构造函数,继续按上面的例子来说,构造函数就是父亲在用自己资源的情况下用某一种方式培养儿子,这里培养的方式就是构造函数,父亲的资源就是原型。其实这个比喻不是很恰当,因为构造函数也可以是可以是一个实例。
什么是实例?什么是构造函数?实例的原型到底是个啥?
首先来说说什么是实例,实例是构造函数返回的对象,而在JS中万物皆可是对象,{}可以是对象,[]可以是对象,function也可以是对象。
再来说说什么是构造函数,在<<Javascript 高级程序设计>> 书中是这样介绍的,任何函数都可以是构造函数
。只要使用new了,任何函数都可以是构造函数。前面又说函数也可以是对象,那么一个函数也可以是一个原型的实例,也就是说一个函数既可以是构造函数也可以是实例。
// 获取一个函数temp的原型
// 函数作为构造函数时,获取对应的原型(函数作为培养方式的时候,获取父亲的资源)
function temp = function() {}
temp.prototype = {a:1} // 设置函数的原型
console.log(temp.prototype) // 函数的原型
// 函数作为一个实例时
function init() {
return () => {}
}
init.prototype = {a:1}
let temp = new init();
console.log(temp.__proto__) // 实例的原型
最后来说一下实例的原型是什么,实例的原型其实就是实例的构造函数对应的原型,用JS代码表示就是
instance.__proto__ == instance.constructor.prototype // true
// 用那个例子来说,instance就是儿子,直接找儿子的原型就是父亲,instance.constructor就是儿子的培养方式。instance.constructor.prototype就是儿子的培养方式用的资源是谁的。
到这里,原型,实例,构造函数的关系我就说明白了,以及原型链我也都用那个例子解释了。
原型链继承
function father() {
this.a = {a: 1} // 问题所在,共享的引用值
}
function son() {
}
// 实现继承操作,这里new的过程中,会返回father原型的实例,并在这个实例上为this的基础上执行father(),返回的实例obj作为son的原型。
son.prototype = new father();
// myson1,myson2 在构造的时候使用的原型都是同一个实例,因此其中一个修改原型上的引用值时,另一个原型上的引用值也随着改变。
let myson1 = new son()
let myson2 = new son()
myson1.__proto__.a.a = 2;
console.log(myson1.a,myson2.a)
盗用构造函数继承
盗用构造函数继承只能继承父类的实例属性和方法,而不能继承父类原型链上的属性和方法。因此,如果需要继承原型链上的属性和方法,可以结合其他方式,例如组合继承或寄生组合式继承等。
funtion father(){
this.a = 1;
}
funtion son(){
father.call(this) // 继承父亲的构造函数,解决引用值问题,但是访问不到父亲的原型
}
let son1 = new son()
let son2 = new son()
组合继承
组合继承通过结合使用原型链继承和经典继承的特点来实现对象之间的共享和扩展。
组合继承的特点包括:
- 通过构造函数继承属性:组合继承使用构造函数来继承属性,子类通过调用父类的构造函数来初始化自己的属性。
- 通过原型链继承方法:组合继承使用原型链来继承方法,子类的原型对象继承自父类的原型对象,从而共享父类的方法。
- 支持重写父类方法:由于子类继承了父类的属性和方法,因此可以通过添加新的属性和方法来扩展或重写父类的行为。
- 可以创建多个实例:由于每个实例都有自己的属性和方法,因此可以创建多个独立的实例。
- 有一定的效率问题:组合继承的一个缺点是在继承时会调用两次父类的构造函数,这可能会影响程序的性能。不过,可以使用其他继承方式来避免这个问题。
funtion father(){
this.a = 1;
}
father.prototype = {b:2}
funtion son(){
father.call(this) // 继承父亲的构造函数,解决引用值问题
}
son.prototype = new father()
let son1 = new son()
let son2 = new son()
原型式继承
我们可以使用 Object.create()
方法来实现原型式继承。这个方法接收一个参数,即新对象的原型对象。它会创建一个新的对象,并将新对象的原型指向传入的原型对象。
原型式继承的特点包括:
- 简单:原型式继承是一种简单的继承方式,不需要定义类或构造函数,只需以现有对象为基础创建新对象即可。
- 共享属性和方法:通过原型链,新对象可以共享原型对象的属性和方法。
- 动态性:原型对象上的属性和方法的变化会立即反映在继承对象上。
- 缺乏封装性和隔离性:由于继承对象与原型对象之间的共享性,修改继承对象的属性可能会影响到其他继承自同一原型对象的对象
function object(prototype) {
function F() {}
F.prototype = prototype;
return new F()
}
寄生式继承
function object(prototype) {
function F() {}
F.prototype = prototype;
return new F()
}
let fatherObj = {a: 1, b:2}
function createAnother(fatherObj) {
let clone = object(fatherObj);
clone.sayHi = function() {
console.log("Hi")
}
}
寄生式组合继承
// 寄生组合继承可以解决父构造函数被调用2次的问题;
function object(prototype) {
function F() {}
F.prototype = prototype;
return new F()
}
function father() {
}
function son() {
}
let protoype = object(father.prototype);
prototype.constructor = son; // 给原型绑定构造函数
son.prototype = prototype; //继承原型
Class继承
ES6(ECMAScript 2015)引入了一种新的语法糖来定义类(class),并且提供了一种更简单和直观的方式来实现类继承。
ES6 类继承的主要特点包括:
- 使用
class
关键字来定义一个类,类名首字母通常大写。 - 使用
constructor
方法来定义类的构造函数,用于初始化对象的属性。 - 使用
extends
关键字来实现继承,子类继承父类的属性和方法。 - 使用
super
关键字来调用父类的构造函数和方法。