JavaScript继承

一、继承的概念

面向对象的三大特征:封装、继承、多态。作用就是判断代码是否是面向对象的思维方式。面向对象的思维方式:注重结果。

继承就是指一个子类继承父类的属性和方法,使得子类对象(实例化对象)具有父类的方法和属性。

这里要明白JavaScript是通过原型链实现继承的。

继承常见的六种方式:原型链继承、构造函数继承、组合继承、原型式继承、寄生式继承、寄生组合继承。

ES6还提供了继承的关键字extends.

接下来详细介绍每种继承方式。

二、继承常见的六种方式

第一种:原型链继承

重点:让子实例对象指向父实例对象

这里需要先理解原型链的基本思想:

构造函数、原型对象,实例对象三者关系,构造函数相当于父亲,原型对象相当于母亲,实例化对象相当于孩子。

    1.prototype属性:属于构造函数,指向原型对象

    作用:解决资源浪费 + 变量污染

    2.__proto__属性:属于实例化对象,指向原型对象

   作用:可以让实例化对象访问原型对象的成员

    3.constructor属性:属于原型对象,指向构造函数

    作用:可以让实例化对象知道自己是被谁创建的(亲子鉴定)

 

把子类的原型对象指向父类,就能使用父类中的方法和属性了。子类的son1.sayName()就能调用了,如果没有Son.prototype = new Father(),son1.sayName()就会报错。

 // 父类
        function Father(name,play){
            this.name = name;
            this.play = [1,2,3]
            this.sayName = function(){
                console.log(this.name); //mini_055
            }
        }
        // 子类
        function Son(){
            this.name = 'mini_055';
        }
        // 原型链继承
        Son.prototype = new Father();
        
        var son1 = new Son()
        son1.sayName()

但是也有两个缺点,

缺点:1.所有子实例对象共用一个原型对象,如果有一个改变则全部都跟着改变;

           2.子实例对象无法向父实例对象传参

第二种:构造函数继承

特点:  1.只继承父类构造函数的成员,没有继承父类原型的成员;2.解决原型链继承的缺点; 3.可以继承多个构造函数的属性(call多个); 4.子实例可以向父实例传参

构造函数继承需要使用call()改变this指向,将this指向Father

 //父类
        function Father(name){
            this.name = 'mini_055'
        }
        Father.prototype.getName = function(){
            return this.name;
        }
        // 子类
        function Son(name){
            // 构造函数继承 
            // call()改变this指向,将Father中的属性添加值Son中
            Father.call(this,name)
            this.type = "Son"
        }

        var son1 = new Son()
        // 运行正常
        console.log(son1)
        // 报错,因为son1中没有getName方法
        son1.getName()

运行结果如下:

通过结果我们可以看到现在子类可以调用父类的属性和方法,但是不能调用父类原型的属性和方法。

缺点:1.只继承父类构造函数的成员(属性和方法),没有继承父类原型的成员;

            2.无法实现构造函数的复用(每次用每次改变父构造函数的this指向); 

            3.每个新实例都有父类构造函数的副本,造成内存臃肿

第三种:组合继承(前两种的组合)

这种方法结合了前两种继承方式的优缺点,形成了第三种继承方式。

 // 父类
        function Father(name){
            this.name = name;
        }
        Father.prototype.aaa = function(aaa){
            console.log(aaa) // aaa的参数
        }
        // 子类
        function Son(name){

            Father.call(this,name)
            
            this.type = "son"
        }

        Son.prototype = new Father();

        var son1 = new Son("mini_055")
        console.log(son1) //Son
        son1.aaa("aaa的参数") 

这种方法可以把前两种方法的缺点都能解决掉,即可以像父元素传参也能改变其中一个属性改变,另一个调用子类的不会改变,不会共享了。

缺点:在使用子类创建实例对象的时候,原型中会存在两份相同的属性和方法。

第四种:原型式继承

特点:类似于复制一个对象,用函数来包装。

采用原型式继承不自定义类型,临时创建一个构造函数,借助已有的对象作为临时构造函数的原型,然后在此基础实例化对象,并返回。

优点:父类方法可以复用

本质上是obj()对传入其中的对象执行了一个浅复制。使用Object.create()创建对象,Object.create() = Object()。

     function obj(a){
            // 父类
            function Father(){}
            Father.prototype = a;
            return new Father()
        }
        // 子类
        var person = {
            name : "mini_055",
            friends:['make','rose','jack']
        }
        var person1 = obj(person);
        person1.name = 'van';
        person1.friends.push('roby');

        var person2 = obj(person);
        person2.name = 'linda';
        person2.friends.push('laila');

        console.log(person.friends) //make、rose、jack、roby、laila

缺点:父类的引用属性会被所有子类实例共享。

            子类构建实例时不能向父类传递参数。

第五种:寄生式继承

核心:使用原型式继承获得一个目标对象的浅复制,然后增强这个浅复制的能力。

通过person对象新返回了一个新对象,新返回的person1对象具有person的所有属性和方法,还有一个新方法是sayHi。

  function obj(a){
            function Father(){} //obj()会创建一个临时构造函数
            Father.prototype = a;//将传入的值赋值给这个构造函数的原型
            return new Father() //返回这个类型的实例,浅复制
        }
        function create(b){
            var clone = obj(b)//通过调用函数创建一个新对象
            clone.sayHi = function(){ 
                console.log('hi'); //Hi
            }
            return clone;//返回这个对象
        }

        var person = {
            name:"mini_055",
            friends:['rose','jack','mark']
        }
        var person1 = create(person)
        person1.sayHi() //打印Hi
        console.log(person1)//Father

运行结果如下:sayHi是person对象本身的新方法,friends是person1对象从perosn继承过来的属性和方法。

 缺点:通过寄生式继承给对象添加函数会导致函数难以重用,与构造函数模式类似。

第六种:寄生组合继承

基本思想:使用寄生式继承来继承父类原型,然后将返回的新对象赋值给子类原型。

 // 父类
        function Father(){
            this.name = ['mini_055'],
            this.getSty = function(){
                console.log('父类的方法');
            }
        }
        Father.prototype.sayHi = function(){
            console.log('Hi');
        }
        // 子类
        function Person(){
            this.personname = ['dis'],
            Father.call(this)//核心代码
        }
        // 核心代码
        Person.prototype = Object.create(Father.prototype)
        var person1 = new Person()
        var person2 = new Person()
        person1.name[0] = 'father'
        person2.name[0] = 'person'
        console.log(person1);
        console.log(person1.name);
        console.log(person2.name);

运行结果如下:

 完美解决原型链加构造函数继承的缺点。

寄生组合继承存在效率问题,最主要的效率问题是父类构造函数始终被调用两次,一次是在创建子类原型时调用,另一次是在子类构造函数中调用,本质上,子类原型最终是要包含父类对象的所有实例属性,子类构造函数只要在执行的时候重写自己的原型就可以了。

缺点:效率低。

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值