• 实现JS继承的几种方法


    总的来说,JS的继承大体上分为两种:借用构造函数方式和原型方式

    首先,我们来看看借用构造函数方式的几种做法:

    //方式一
    function
    Person(name, sex){ this.name = name; this.sex = sex; this.move = function(){ alert("move"); }; }; function Man(name, sex, address, phone){ this.pe = Person; this.pe(name, sex); delete this.pe; this.address = address; this.phone = phone; };

    原理是这样的,将整个方法替换掉pe,当我们调用 this.(name,sex); 时,

    相当于在Man里面执行上面的那一段代码,而此时的this已经代表的是Man对象,使Man也具有了name,sex等属性与及move方法。

     

    //方式二
    function Person(name, sex){
        this.name = name;
        this.sex = sex;
        this.move = function(){
            alert("move");
        };
    };
    
    function Man(name, sex, address, phone){
        Person.call(this, name, sex);
        this.address = address;
        this.phone = phone;
    };

    call方法实现继承,call方法会将this及Man中的参数传递到调用的方法(Person)中,此时Person里的this代表的是Man对象

    当调用到Person的构造方法的时候,调用this.name的时候已经是Man.name了,这种方法也可以实现继承。

     

    //方式三
    function Person(name, sex){
        this.name = name;
        this.sex = sex;
        this.move = function(){
            alert("move");
        };
    };
    
    function Man(name, sex, address, phone){
        Person.apply(this, new Array(name, sex));
        this.address = address;
        this.phone = phone;
    };

    apply方法实现继承,其实apply方法和call方法是一样,只不过apply传递过去的参数要用一个数组包装起来而已。

     

    上面就是借用构造函数方式的三种做法,原理其实就是上下文环境变量this的替换。

    它可以实现多重继承,但真正这样用的不多,因为它有着明显的性能缺陷。

    借用构造函数方式模拟的继承里,所有的成员方法都是针对this而创建的,也就是所有的实例都会拥有一份成员方法的副本,这是对内存资源的一种极度浪费。

    还有就是借用构造函数方式无法继承prototype域的变量和方法。

     

    再来看看原型方式:

    function Person(){};
    Person.prototype.name = "";
    Person.prototype.sex = "";
    Person.prototype.move = function(){
        alert("move");
    };
    function Man(){}; Man.prototype = new Person();

    关键是对最后一句Man的原型指向Person类构造的对象(注意是对象,是实例)。

    Js对象在读取某个对象属性的时候,总是先查看自身域的属性列表,如果有就返回,否则去读取prototype域(每个对象共享构造对象的类的prototype域所有属性和方法),如果找到就返回,由于prototype可以指向别的对象,所以Js解释器会递归的去查找prototype域指向对象的prototype域,直到prototype为本身,查找变成了一种循环,就停止,此时还没找到就成undefined了。

    这样看来,最后一句发生的效果就是将父类所有属性和方法连接到子类的prototype域上,这样子类就继承了父类所有的属性和方法。

    原型继承的缺陷也相当明显,就是继承时父类的构造函数时不能带参数,以及不支持多继承。

     

    综合两种方式的利弊,可以总结出一种混合式继承方法:

    function Person(name){
        this.name = name;    
    };
    Person.prototype.move = function(){
        alert("move");
    };   
     
    function Man(name){
        Person.call(this, name);
    };
    Man.prototype = new Person();
    Man.prototype.showName = function(){
        alert("My name is " + this.name + ".");
    };

    用借用构造函数方式来继承父类属性,用原型方式来继承父类的方法。这样在对子类进行实例化的时候,就可以同时初始化从父类继承下来的属性。

    同时,由于父类的方法都是定义在prototype域上,通过原型方式继承,这样就不会造成资源的浪费。

    美中不足就是始终不支持多继承,但足可满足大多数情况的需要了。

    除去上面的几种方式外,这里再补充一种名为寄生组合式的继承方式:

    function inheritPrototype(subClass, superClass){
        var prototype = Object(superClass.prototype);
        prototype.constructor = subClass;
        subClass.prototype = prototype;    
    }
    
    function Person(name){
        this.name = name;    
    };
    Person.prototype.move = function(){
        alert("move");
    };   
     
    function Man(name){
        Person.call(this, name);
    };
    inheritPrototype(Man, Person);
    Man.prototype.showName = function(){
        alert("My name is " + this.name + ".");
    };
    
    var person = new Person();
    console.info(person  instanceof Man); // 结果是true,这并非是我们想要的,是此方法的缺点

    与上面混合方式相比,都使用了借用构造函数的方式来继承父类属性。

    但这里没有把子类的原型指向父类的实例,而是直接把子类的原型指向改写了constructor属性的父类原型

    以此来实现了原型链,达到继承的目的。

    这种方式的优点是,实现继承只调用了一次父类的构造函数。缺点是,父类的实例使用instanceof操作符判断它是子类的实例时也会返回true。另外,父类的cunstructior属性也跟着指向了子类的构造函数了。显然这并非是我们想要的。

    这种方法,实际上也相当于所有类共用同一个原型,试想一下,当出现复杂的继承关系,原型就变得很难维护了(容易出现方法重写覆盖的情况)。

    所以,个人还是更推荐用前一种方法,混合式继承。  

  • 相关阅读:
    Token的生成和检验
    MD5和SHA加密实现
    服务器读取客户端数据
    服务器上传和下载文件
    NOI模拟题4 Problem B: 小狐狸(fox)
    NOI模拟题4 Problem A: 生成树(mst)
    混凝土数学第四章之数论学习笔记
    混凝土数学第二章和式之有限微积分 ( 离散微积分 ) 学习笔记
    网络流相关学习笔记
    NOI模拟题1 Problem A: sub
  • 原文地址:https://www.cnblogs.com/czf-zone/p/3599522.html
Copyright © 2020-2023  润新知