美文网首页
js中的继承-原型与原型链

js中的继承-原型与原型链

作者: jadefan | 来源:发表于2019-10-13 10:50 被阅读0次

    面向对象的语言支持两种继承方式,接口继承和实现继承
    js无法实现接口继承,只支持实现继承,主要通过原型链来实现。
    具体实现方式有以下几种:

    • 原型链
    • 借用构造函数
    • 组合继承
    • 原型式继承
    • 寄生式继承
    • 寄生组合式继承
      逐一看下:

    原型链

    每个函数都有prototype(原型)属性,指向一个原型对象
    原型对象都包含一个指向构造函数的指针(constructor)
    通过函数实例化的对象中,都有一个指向原型对象的内部指针

    通过这种特性,可以让一个对象A的原型对象等于另一个实例化的对象B,对象B的原型对象等于另一个实例化的对象C,这样层层递进,就构成了实例与原型的链条,也就是原型链

        function Person() {
          this.name = 'wang'
        } 
        Person.prototype.sayName = function () {
          console.log(this.name);
        }
    
        function Man() {
          this.age = 18;
        }
        //继承:子类的原型等于父类的实例对象
        Man.prototype = new Person(); 
        Man.prototype.sayAge = function () {
          console.log(this.age);
        }
    
        var p1 = new Man();
        p1.sayName();  //wang
        p1.sayAge();   //18
    

    代码中子类的方法都可以正常使用,这就涉及到了对象的原型搜索机制

    当访问一个实例属性时,首先在实例中搜索该属性,如果没有找到,则会继续搜索实例的原型,如果没有找到并且原型对象还有原型,则继续向上搜索,直到搜索到没有原型对象为止,也就是截止到Object,因为js中所有对象都继承自Object。
    这也就能解释为什么所有实例都有toString()等公用方法。

    对象识别

    利用原型链继承实现的子类可以识别属于子类、父类、Object

        console.log(p1 instanceof Object);  //true
        console.log(p1 instanceof Person);  //true
        console.log(p1 instanceof Man);     //true
    
    派生判断
        console.log(Object.prototype.isPrototypeOf(p1));  //true
        console.log(Person.prototype.isPrototypeOf(p1));  //true
        console.log(Man.prototype.isPrototypeOf(p1));     //true
    
    原型链的问题:
    1. 属性为引用类型时,由于共享机制,操作实例属性会影响其他实例
    2. 无法在不影响其他实例的情况下,给父类的构造函数传参
        function Person() {
          this.name = 'wang'
          this.friends = ['zhang', 'liu']
        } 
    
        function Man() {
          this.age = 18;
        }
        //继承:子类的原型等于父类的实例对象
        Man.prototype = new Person();
    
        var p1 = new Man();
        var p2 = new Man();
    
        console.log(p1.friends);  //["zhang", "liu"]
        p2.friends.push('li');
        console.log(p1.friends);  //["zhang", "liu", "li"]
    

    可以看到数组属性受到影响,所以实践中很少单独使用原型链。

    借用构造函数

    也叫伪造对象或经典继承,为解决原型链不足而生

    基本思想:在子类构造函数内部调用父类构造函数

        function Person(name) {
          this.name = name;
          this.friends = ['zhang', 'liu']
        }
    
        function Man(name) {
          Person.call(this, name)
        }
    
        var p1 = new Man('wang');
        var p2 = new Man('zhang');
        console.log(p1.name);  //wang
        console.log(p2.name);  //zhang
        console.log(p1.friends);  //["zhang", "liu"]
        p2.friends.push('li');
        console.log(p2.friends);  //["zhang", "liu", "li"]
        console.log(p1.friends);  //["zhang", "liu"]
    

    可以看出即可以给父类的构造函数传参,引用类型的属性又互不影响

    问题

    如果仅用借用构造函数,就会碰到构造函数的共性问题,无法将函数复用造成资源浪费,所以借用构造函数的技术也很少单独使用。

    组合继承

    也叫伪经典继承,将原型链和借用构造函数组合来用,发挥二者之长
    使用原型链实现对原型属性和方法的继承
    使用借用构造函数实现对实例属性的继承

        function Person(name) {
          this.name = name;
          this.friends = ['zhang', 'liu']
        }
        Person.prototype.sayName = function () {
          console.log(this.name);
        } 
        
        function Man(name, age) {
          //用构造函数继承属性
          Person.call(this, name);
          this.age = age;
        }
        //用原型链继承实例
        Man.prototype = new Person();
        Man.prototype.sayAge = function () {
          console.log(this.age);
        }
    
        var p1 = new Man('wang', 18);
        console.log(p1.name); //wang
        p1.sayName()  //wang
        p1.sayAge();  //18
    
        console.log(p1.friends);  //["zhang", "liu"]
        console.log(p1.__proto__.friends);  //["zhang", "liu"]
    

    代码中用到了2种方式一起实现了继承
    通过对象的__proto__属性,可以访问到对象的原型
    可以看到实例对象和实例的原型对象同时存在父类属性和方法
    组合继承避免了各自的缺陷,融合了优点,成为最常用的继承模式
    但也有不足之处:

    ...
    Person.call(this, name);  //第二次执行Person()
    ...
    Man.prototype = new Person();  //第一次执行Person()
    

    组合继承会指向两次父类的构造函数,在性能上不够理想
    所以可以采用寄生组合式继承优化实现

    在不同场景下,还有其他可选择的轻量级继承方式

    原型式继承

    核心思想:原型可以基于已有对象创建新对象,同时不必创建自定义类型

        function object(obj) {
          function Fn() { }
          Fn.prototype = obj;
          return new Fn();
        }
    

    在函数内部,先创建了一个临时的构造函数,再将传入的对象作为这个构造函数的原型,最后返回这个临时类型的一个新实例。本质是执行了传入对象的浅复制。

        var person = {
          name: 'wang',
        } 
        var p1 = object(person);
        console.log(p1.name);
    

    在ES5中通过新增Object.create()方法规范了原型式继承

        var person = {
          name: 'wang',
          friends: ['zhang', 'liu']
        }
        var p1 = Object.create(person);
        console.log(p1.friends);  //["zhang", "liu"]
        var p2 = Object.create(person);
        p2.friends.push('li');
        console.log(p1.friends);  //["zhang", "liu", "li"]
    

    通过Object.create()可实现原型式继承
    这种方式适用于让一个对象与另一个对象保持类似的情况,可不用构造函数
    问题:可以看出,存在引用类型的属性的共享问题,也就是会被其它实例修改

    寄生式继承

    实现思路:创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后返回对象

        var person = { 
          name: 'wang',
          friends: ['zhang', 'liu']
        }
        function createObject(obj){
          var copy = Object.create(obj);
          copy.sayName = function(){
            console.log(this.name);
          }
          return copy;
        }
    
        var p1 = createObject(person);
        p1.sayName();  //wang
    

    类似寄生构造函数和工厂模式,
    适用于:主要考虑对象而不是自定义类型和构造函数的情况
    问题:在函数体内部定义方法,会无法使函数复用而降低效率

    寄生组合式继承

    针对组合式继承模式指向两次父类构造函数的不足,来优化

    //实现父类原型拷贝副本给子类
        function inheritPrototype(subObj, superObj) {
          var prototype = Object.create(superObj.prototype);  //创建对象
          prototype.constructor = subObj;                     //增强对象
          subObj.prototype = prototype;                       //指定对象
        }
    
        function Person(name) {
          this.name = name;
          this.friends = ['zhang', 'liu']
        }
        Person.prototype.sayName = function () {
          console.log(this.name);
        }
    
        function Man(name, age) {
          //用构造函数继承属性,只需执行一次Person()
          Person.call(this, name);
          this.age = age;
        } 
    
        //Man.prototype = new Person();
        inheritPrototype(Man, Person);       //拷贝父类原型的副本,无需执行构造函数
        Man.prototype.sayAge = function () {
          console.log(this.age);
        }
     
        var p1 = new Man('wang', 18);
        console.log(p1.name); //wang
        p1.sayName()  //wang
    

    寄生组合式继承是引用类型最理想的继承方式。

    相关文章

      网友评论

          本文标题:js中的继承-原型与原型链

          本文链接:https://www.haomeiwen.com/subject/jvyhmctx.html