32

前端面试基础回顾之深入JS继承

 4 years ago
source link: http://www.cnblogs.com/pqjwyn/p/12168520.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

前言

对于灵活的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,每周三个知识点回顾。要去相信,你若盛开蝴蝶自来。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK