JS-面向对象-继承

欢迎大家到访我的github: https://github.com/suilfly
我的个人博客:https://suilfly.github.io/

实现继承是 ECMAScript 唯一支持的继承方式,而这主要是通过原型链实现的

原型链继承

构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型有一个属性(constructor)指回构造函数,而实例有一个内部指针([[ prototype ]])指向原型。

 /* 构造函数 */
function Father(){
    this.fatherName="f1";
}
console.dir(Father);/*构造函数内有prototype: {constructor: ƒ Father()} constructor指向father */
let fatherInstance = new Father();
console.log(fatherInstance.__proto__);/* fatherInstance为Father的实例:实例中有一个指针指向Father原型,输出{constructor: ƒ Father()} */

js红宝书中的插图

实现继承

function Father(){
    this.fatherName="f1";
}
Father.prototype.f = "pf"
function Son(){
    this.sonName="s1";
}
/* 现在想要Son继承Father,已知Son的构造函数的prototype:{constructor f Son()} ,那么只要改变Son的构造函数的原型即可*/
/* Son.prototype = Father.prototype; 不能使用此方法改变Son的原型,因为如此改变后,Son的实例只能使用自己的属性和Father原型上的属性,并没有继承Father实例中的属性,而且以后想要修改Son的原型必定会破坏Father的原型*/
Son.prototype = new Father();
console.log(s instanceof Father);//true
console.log(s instanceof Son);//true
  • 现在想要Son继承Father,已知Son的构造函数的prototype:{constructor f Son()} ,那么只要改变Son的构造函数的原型即可
  • Son.prototype = Father.prototype; 不能使用此方法改变Son的原型,因为如此改变后,Son的实例只能使用自己的属性和Father原型上的属性,并没有继承Father实例中的属性,而且以后想要修改Son的原型必定会破坏Father的原型

原型与实例的关系

  • 方式一
/* 上面用到了instanceof来检测原型与实例的关系 */
console.log(s instanceof Object);//true,找到s原型链上有和Object一样的构造函数
console.log(s instanceof Father);//true
console.log(s instanceof Son);//true
/* instanceof的原理:查找s的原型链,看看是否有和Object|Father|Son原型链上一样的构造函数 */
  • 方式二

    • 确定这种关系的第二种方式是使用 isPrototypeOf()方法。原型链中的每个原型都可以调用这个方法,只要原型链中包含这个原型,这个方法就返回 true:
Father.prototype.isPrototypeOf(s);//true ,s的原型链中包含Father原型
Son.prototype.isPrototypeOf(s);//true ,s的原型链中包含Father原型
Object.prototype.isPrototypeOf(s);//true ,s的原型链中包含Father原型

原型链的问题

  • 某一实例成为构造函数原型后,共享内存的问题
function Father(){
    this.fatherFood=['面条','鸡蛋'];
}
function Son(){
    this.sonFoos = ['肯德基']
}
/* 继承 */
Son.prototype = new Father();

let s = new Son();
s.fatherFood.push('鸡腿');
let s2 = new Son();
s2.fatherFood;//["面条", "鸡蛋", "鸡腿"],但是s2的fatherFood也改变

  • 原型链的第二个问题是,子类型在实例化时不能给父类型的构造函数传参。就导致原型链基本不会被单独使用,用来修改继承关系。

盗用构造函数继承

function Father(name){
    this.fatherFood=['面条','鸡蛋'];
    this.name = "ff";
}
function Son(){
    this.sonFoos = ['肯德基'];
    Father.call(this,"ss");/* 执行Father,并修改this指向Son的实例,可以在子类构造函数中向父类构造函数传参 */
}

let s = new Son();
s.fatherFood.push('鸡腿');
let s2 = new Son();
s2.fatherFood;//["面条", "鸡蛋"],解决了共享内存问题

s instanceof Father;//false
s instanceof Son;//true

  • 盗用构造函数的问题:子类也不能访问父类原型上定义的方法,所以基本也不用此方法来修改继承关系

组合继承

组合继承(有时候也叫伪经典继承)综合了原型链和盗用构造函数,将两者的优点集中了起来。基本的思路是使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性

function Father(name){
    this.fatherFood=['面条','鸡蛋'];
    this.name = "ff";
}
function Son(){
    this.sonFoos = ['肯德基'];
    /* 继承属性 */
    Father.call(this,"ss");
}
Son.prototype = new Father();/* 继承父方法 */

let s = new Son();
s.fatherFood.push('鸡腿');
let s2 = new Son();
s2.fatherFood;//["面条", "鸡蛋"],解决了共享内存问题

  • 组合继承弥补了原型链和盗用构造函数的不足,是 JavaScript 中使用最多的继承模式。而且组合继承也保留了 instanceof 操作符和 isPrototypeOf()方法识别合成对象的能力。

寄生式组合继承

  • 其实组合继承也有一个问题:效率不是很高,因为它要执行两次父级构造函数,第一次在子级构造函数内,第二次在修改子级原型时
/* 寄生式顾名思义,在一个函数内部做了某些操作 */
function inheritPrototype(Father,Son){
    let prototype = Object.create(Father.prototype);/* Object.create(proto,[propertiesObject]);proto 新创建对象的原型对象 */
    prototype.constructor = Son;//修改constructor,因为之后要把这个prototype作为Son的原型,那么Son原型中的constructor一定要指向Son啦
    Son.prototype = prototype;
}

function Father(name){
    this.fatherFood=['面条','鸡蛋'];
    this.name = "ff";
}
Father.prototype.ff = function(){ console.log("function of father") }
function Son(){
    this.sonFoos = ['肯德基'];
    /* 继承属性 */
    Father.call(this,"ss");
}
inheritPrototype(Father,Son);

  • 这里只调用了一次 Father 构造函数,避免了 Son.prototype 上不必要也用不到的属性,
    因此可以说这个例子的效率更高。而且,原型链仍然保持不变,因此 instanceof 操作符和
    isPrototypeOf()方法正常有效。寄生式组合继承可以算是引用类型继承的最佳模式
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值