js中的基础继承——原型链继承,经典继承,组合继承

继承思想源于面向对象编程语言,一般情况下支持两种继承方式:接口继承和实现继承。前者只继承方法签名,后者继承实际方法。

而在JS中,接口继承是不可能实现的,因为JS中函数并没有签名。实现继承是JS唯一支持的方式,这主要通过原型链实现的。

原型链

预备知识

每个构造函数都有一个原型对象,原型有一个属性指回构造函数,而实例有一个内部指针指向原型。
在这里插入图片描述
实例对象可以调用原型对象的属性和方法。

原型链的使用

实现思想:“子类”实例对象的原型是“父类”的实例对象(在es5中并没有子类和父类这一概念 只是为了方便读者理解),实现“子类”的实例对象有一指针指向“子类”原型,而“子类”原型又有一指针指向“父类”原型。
大致图解如下
在这里插入图片描述
实现代码:

// 创建Animal
function Animal() {
  this.name = 'animal';
}
Animal.prototype.getAnimalName = function () {
  console.log(this.name + 'getAnimalName');
}
// 创建Dog
function Dog() {
  this.name = 'dog';
}
// Dog继承自Animal  将Animal的实例赋值给Dog的原型对象,相当于将Animal的实例中的__proto__赋值给了Dog的原型对象
// 如此 Dog原型对象 就能通过 Animal 对象的实例中的[[prototype]](__proto__) 来访问到 Animal原型对象 中的属性和方法了。
Dog.prototype = new Animal();//核心思想体现代码

console.log(Dog.prototype.__proto__ === Animal.prototype );
// 在使用原型链继承的时候,要在继承之后再去原型对象上定义自己所需的属性和方法
Dog.prototype.getDogName = function () {
  console.log(this.name + 'getDogName');
}
var d1 = new Dog();
d1.getAnimalName()
d1.getDogName()

原型链拼接的核心代码就是使“子类”原型对象称为“父类”构造函数构造的实例对象。

原型链的破坏

如果以字面量的形式重写“子类”原型对象,会导致其不再属于“父类”构造函数构造出来的实例,从而破坏原型链。

原型链的缺陷

此类继承思想有部分缺陷:

  1. 众所周知,原型对象的constructor指向他的构造函数,即“子类”的原型对象指向“子类”构造函数,而通过原型链继承后,“子类”的原型对象成为了“父类”构造函数的实例化对象,导致“子类”的原型对象中的constructor属性指向了“父类”构造函数,这显然不符合语义,可以通过子类.prototype.constructor=子类 来修正指针
  2. 由于引用数据类型的存储方式比较特殊,在子类实例中修改父类中的属性(引用数据类型)时,另一个子类也会收到影响。
function Animal() {
  this.categorys = ["cat", "rabbit"];
}
function Dog() { }
// 继承 Animal 
Dog.prototype = new Animal();
var d1 = new Dog();
d1.categorys.push("dog");
console.log(d1.categorys); // [ 'cat', 'rabbit', 'dog' ]
var d2 = new Dog();
console.log(d2.categorys); // [ 'cat', 'rabbit', 'dog' ]

经典继承

为了解决原型中包含引用数据类型的属性继承问题,一种“盗用构造函数”的继承思想应运而生。

实现思想:在子类构造函数中调用父类构造函数。

function Animal() {
  this.categorys = ["cat", "rabbit"];
}
function Dog() {
  // 继承 Animal 
  Animal.call(this);
}

在以上代码中,子类dog构造函数创建出来的实例对象也拥有父类构造函数Animal中的所有属性。
实际上,这并不是一种继承,而是一种盗用,本质上并没有拼接原型链。

经典继承的缺陷

对于私有属性来说,这种继承方式会将属性分别存储在各个实例对象中,互不干扰;可对于一些我们想要共有的属性,方法,这种继承方式就无法实现了,函数的特点就是复用性,一次声明多次调用,不会占用多份内存,而这种经典继承方式将相同的成员函数分别声明在各个实例中,浪费了内存,影响性能。

组合继承

这是一种综合了原型链和经典继承函数的继承方式,将两者的优点结合了起来。

实现思路:将需要私有化的属性使用经典继承的方式继承,使用拼接原型链的方式继承共有的原型方法和属性。这样既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性。

function Animal(name) {
  this.name = name;
  this.categorys = ["cat", "rabbit"];
}
Animal.prototype.sayName = function () {
  console.log(this.name);
};
function Dog(name, age) {
  // 继承属性
  Animal.call(this, name);
  this.age = age;
}
// 继承方法
Dog.prototype = new Animal();
Dog.prototype.sayAge = function () {
  console.log(this.age);
};
var d1 = new Dog("zhangsan", 29);
d1.categorys.push("dog");
console.log(d1.categorys); // [ 'cat', 'rabbit', 'dog' ]
d1.sayName(); // zhangsan
d1.sayAge(); // 29 
var d2 = new Dog("lisi", 27);
console.log(d2.categorys); // [ 'cat', 'rabbit' ]
d2.sayName(); // lisi
d2.sayAge(); // 27
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值