深入JS继承

2022-04-15 0 1,010
目录
  • 前言
  • 准备
  • 总结
  • 继承的n种方式
    • 原型式继承
    • 原型链式继承
    • 借用构造函数(类式继承)
    • 组合继承
    • 寄生组合式继承
  • 结束语

    前言

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

    以上就是深入JS继承的详细内容,更多关于深入JS继承的资料请关注NICE源码其它相关文章!

    免责声明:
    1、本网站所有发布的源码、软件和资料均为收集各大资源网站整理而来;仅限用于学习和研究目的,您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。 不得使用于非法商业用途,不得违反国家法律。否则后果自负!

    2、本站信息来自网络,版权争议与本站无关。一切关于该资源商业行为与www.niceym.com无关。
    如果您喜欢该程序,请支持正版源码、软件,购买注册,得到更好的正版服务。
    如有侵犯你版权的,请邮件与我们联系处理(邮箱:skknet@qq.com),本站将立即改正。

    NICE源码网 JavaScript 深入JS继承 https://www.niceym.com/34299.html