深入JS继承

更新日期: 2019-12-09阅读: 1.6k标签: 继承

前言

对于灵活的js而言,继承相比于java等语言,继承实现方式可谓百花齐放。方式的多样就意味着知识点繁多,当然也是面试时绕不开的点。撇开ES6 class不谈,传统的继承方式你知道几种?每种实现原理是什么,优劣点能谈谈吗。这里就结合具体例子,按照渐进式的思路来看看继承的发展。


准备

谈到js继承之前先回顾下js 实例化对象的实现方式。

构造函数是指可以通过new 来实例化对象的函数,目的就是为了复用,避免每次都手动声明对象实例。

new 简单实现如下:

function my_new(func){
    var obj = {}
    obj._proto_ = func.prototype // 修改原型链指向,拼接至func原型链
    func.call(obj) // 实例属性赋值
    return obj
}

由上可以看出,通过构造函数调用,可以将实例属性赋值到目标对象上。

如此可以推想,子类中调用父类构造函数同样可以达到继承的目的。

这就提供了js继承的一种思路,即通过构造函数调用。

至于原型属性,就是通过修改原型指向,来实现原型属性的共享。

那么继承时同样也可以通过该方式进行。

总结

基于构造函数和原型链两种特性,结合js语言的灵活性。

继承的实现方式虽然繁多万变也不离其宗


继承的n种方式

原型式继承

定义:这种继承借助原型并基于已有的对象创建新对象,同时还不用创建自定义类型的方式称为原型式继承。

直接看代码更清晰:

function createObj(o) {
  function F() { }
  F.prototype = o;
  return new F();
}
var parent = {
  name: 'trigkit4',
  arr: ['brother', 'sister', 'baba']
};
var child1 = createObj(parent);

该方式表面上看基于对象创建,不需要构造函数(当然实际构造函数被封装起来罢了)。只借助了原型对象,所以名称为原型式继承。

缺点

比较明显优良者

无法复用该继承,每个子类的实例,都要走完整的createObj流程。

对于子类对象
因为构造函数封装createObj中,对其而言,没有构造函数。由此造成无法初始化时传参。
补充:其中 createObj 就是我们ES6中常用的Object.create(),不过Object.create进行了完善,允许额外参数来完善了。

解决思路

既然提到没有构造函数导致了问题,那么大胆猜测,更进一步就是涉及了构造函数的原型链继承了。


原型链式继承

定义:为了让子类继承父类的属性(也包括方法),首先需要定义一个构造函数。然后,将父类的新实例赋值给构造函数的原型。

function Parent() {
  this.name = 'mike';
}
function Child() {
  this.age = 12;
}
Child.prototype = new Parent();
child.prototype.contructor = child // 原型属性被覆盖,所以要修正回来。
var child1 = new Child();

也就是直接修改子类的原型对象指父构造函数的实例,这样把父类的实例属性和原型属性都挂到自己原型链上。

缺点

Child.prototype = new Parent() ,那么子函数自身的原型属性就被覆盖了,如果需要就要在后面补充。

子对象实例化时,无法向父类构造函数传递参数。
例如在new Child()执行的时候,想要去覆盖name,只能在Child.prototype = new Parent()时。 是我们在new Child()的时候统一传参初始化是更常规需求。

解决思路

如何在子类初始化时,调用父类构造函数。结合前面的基础,答案也呼之欲出。


借用构造函数(类式继承)

类式继承:是在子类型构造函数的内部调用超类型的构造函数。

思路比较清晰,由问题驱动。

既然原型链式子类不能向父类传参的问题,那么在子类初始化是调用父类不就满足目的了。

示例如下:

function Parent(age) {
  this.name = ['mike', 'jack', 'smith'];
  this.age = age;
}
Parent.prototype.run = function () {
  return this.name + ' are both' + this.age;
};
function Child(age) {
  // 调用父类
  Parent.call(this, age);
}
var child1 = new Child(21);

这样满足了初始化时传参的需求,但是问题也比较明显。

child1.run //undefined

问题

父类原型属性丢失

父类初始化只继承了示例属性,原型属性在子类的原型链上丢失

解决思路

丢失的原因在于原型链没有修改指向,那么修改下指向不就完了。


组合继承

定义:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承

示例:

function Parent(age) {
  this.name = ['mike', 'jack', 'smith'];
  this.age = age;
}
Parent.prototype.run = function () {
  return this.name + ' are both' + this.age;
};
function Child(age) {
  // 调用父类构造函数
  Parent.call(this, age);
}
Child.prototype = new Parent();//原型属性继承
Child.prototype.contructor = Child
var child1 = new Child(21);

这样问题就避免了:

child1.run() // "mike,jack,smith are both21"

问题

功能满足之后,就该关注性能了。这种继承方式问题在于父类构造函数执行了两次。

分别是:

function Child(age) {
  // 调用父类构造函数,第二次
  Parent.call(this, age);
}
Child.prototype = new Parent();//修改原型链指向,第一次

解决思路

解决自然是取消一次构造函数调用,要取消自然要分析这两次执行,功能上是否有重复。

第一次同样继承了实例和原型属性,第二次执行同样继承了父类的实例属性。

因此第二次满足对父类传参的不可获取性,因此只能思考能否第一次不调用父类构造函数,只继承原型属性。

答案自然是能,前面原型式继承就是这个思路。


寄生组合式继承

顾名思义,寄生指的是将继承原型属性的方法封装在特定方法中,组合的是将构造函数继承组合起来,补充原型式继承的不足。

饶了点,直接看:

function createObj(o) {
  function F() { }
  F.prototype = o;
  return new F();
}
//继承原型属性 即原型式继承
function create(parent, child) { 
  var f = createObj(parent.prototype);//获取原型对象
  child.prototype = f
  child.prototype.constructor = child;//增强对象原型,即保持原有constructor指向
}

function Parent(name) {
  this.name = name;
  this.arr = ['brother', 'sister', 'parents'];
}
Parent.prototype.run = function () {
  return this.name;
};
function Child(name, age) {
  // 示例属性
  Parent.call(this, name);
  this.age = age;
}
// 原型属性继承寄生于该方法中
create(Parent.prototype,Child);
var child1 = new Child('trigkit4', 21);

这样沿着发现问题解决问题的思路直到相对完善的继承方式。至于ES的方式本篇就不涉及了。


结束语

唯有厚积,才能薄发,想要心仪的offer,就得准备充裕,夯实基础,切忌似是而非,道理我都懂就是答得不完全,这样跟不懂差别也不太大。不算新的日子里立个flag,每周三个知识点回顾。要去相信,你若盛开蝴蝶自来。

链接: https://fly63.com/article/detial/7217

js原型继承、构造函数继承、组合继承法

原型继承缺点:1、不能由子对象像父对象传递参数,2、对于引用型的属性修改之后会印象其他的实例对象;构造函数继承缺点:1、不能继承父对象原型上的方法 2、每次实例化对象会重新构建函数,浪费内存。

各种实现js继承的方法总结

学过java的同学应该都知道,继承是java的重要特点之一,许多面向对象的语言都支持两种继承方式:接口继承和实现继承,接口继承只继承方法签名,而实现继承则继承实际的方法,在js中,由于函数没有签名,因此支持实现继承,而实现继承主要是依靠原型链来实现的,那么,什么是原型链呢?

css的继承性

在面向对象语言都会存在继承的概念,在面向对象语言中,继承的特点:继承了父类的属性和方法。那么我们现在主要研究css,css就是在设置属性的。不会牵扯到方法的层面。

js原型继承的几种方式

1. 原型链继承;2,构造函数继承(对象冒充继承);3,组合继承(原型链继承+构造函数继承);4,原型式继承;5. 寄生组合式继承,为了解决引用共享和超类型无法传参的问题,我们采用一种叫借用构造函数的技术

Js继承背后的场景-prototype,__proto__, [[prototype]]

prototype是构造函数的一个属性,它决定了在构造出来的对象上__proto__属性将是什么样的。如上图所示,理解JavaScript中的继承的关键是要理解母鸡如何产蛋的过程。

Js常用的继承方式

JavaScript常用继承方式主要分为(7种):原型链继承、构造函数继承、组合继承、原型式继承、寄生式继承、寄生组合继承以及继承多个对象。原型链继承(核心:将父类的实例作为子类的原型

JS之继承(ES5 & ES6)

继承到底是什么?继承(英语:inheritance)是面向对象软件技术当中的一个概念。如果一个类别B“继承自”另一个类别A,就把这个B称为“A的子类”,而把A称为“B的父类别”也可以称“A是B的超类”。继承可以使得子类具有父类别的各种属性和方法

Js继承总结

当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止。如果让原型对象指向另一个类型的实例.....有趣的事情便发生了.

ES5 的构造函数原型链继承

构造函数,就是专门用来生成实例对象的函数。一个构造函数,可以生成多个实例对象,这些实例对象都有相同的结构。为了与普通函数区别,构造函数名字的第一个字母通常大写。

Js对象继承

JavaScript对象继承的方法有很多,这里总结一下几种比较常用的方法。使用call/apply和Object.create()第一种方法使用call或apply方法,改变了 this 的指向而实现继承,将父对象的构造函数绑定在子对象上,即在子对象构造函数中加一行

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!