JS之继承和实现继承的方法

一、什么是继承?

首先了解一下什么是原型链:
JavaScript 规定,所有对象都有自己的原型对象(prototype)。一方面,任何一个对象,都可以充当其他对象的原型;另一方面,由于原型对象也是对象,所以它也有自己的原型。因此,就会形成一个“原型链”(prototype chain):对象到原型,再到原型的原型……(原型链的尽头就是null)
也就是,访问一个对象的属性时,先在基本属性中查找,如果没有,再沿着_proto_这条链向上找,这就是原型链。

那么,如何判断一个属性到底是基本的还是从原型中找到的呢?这就要用到hasOwnProperty():

		function Dog(type) {
            this.type = type;
            this.say = 'wangwang';
        }
		//在原型对象中添加一个属性
        Dog.prototype.name = '阿牧';
        const dog = new Dog('德牧');
        console.log(dog);
        //自身没有name属性,是继承来的
        //hasOwnProperty()判断属性或方法是否是自身的
        console.log(dog.hasOwnProperty('name')); //false
        console.log(dog.hasOwnProperty('say')); //true

打开控制台界面如下:
在这里插入图片描述
可以看到,name属性是从原型中找到的,并不是他自身的基本属性,而这个hasOwnProperty属性也是从Object.prototype中来的。
对象的原型链是沿着__proto__这条线走的,因此在查找dog.hasOwnProperty属性时,就会顺着原型链一直查找到Object.prototype。
由于所有对象的原型链都会找到Object.prototype,因此所有对象都会有Object.prototype的方法。这就是所谓的“继承”。

二、实现继承的方法

1、原型继承

实例化父类,让它作为子类的原型,从而实现子类可以访问到父类构造函数以及原型上的属性或者方法。
优点是简单易于实现,父类新增的实例与属性子类都能访问到;
缺点是在创建子类型的实例时,没有办法在不影响所有对象实例的情况下给父类传递参数。

		function Person(name , age) {  
            this.name = name ; 
            this.age = age ;
            this.type = '人' ; 
        }

        Person.prototype.say = function () {  
            console.log('我是人');
        }

        function YellowPerson(name , age) {  
            this.color = 'yellow' ;
            this.talk = function () {  
                console.log('我黄种人');
            }
        }
        YellowPerson.prototype = new Person('cc' , 18) ;

        const y = new YellowPerson('yy' , 6) ;
        console.log(y);

        console.log(y.type);
        y.say()

在这里插入图片描述
可以看到,const y = new YellowPerson('yy' , 6) 这条子类的参数并没有传上去。

2、构造函数继承

原理是调用父类的构造函数然后改变他的this指向,也就是复制父类的实例属性给子类
这样做的优点是解决了子类构造函数向父类传递参数的问题,可以实现多继承(call或apply多个父类),解决了原型中包含实例引用类型值被所有实例共享的问题;
缺点是不能继承原型属性、方法,只能继承父类的实例属性和方法,方法都在构造函数中定义,无法复用;

		function Person(name, age) {
            this.name = name;
            this.age = age;
            this.type = '人';
        }

        Person.prototype.say = function() {
            console.log('我是人');
        }


        function YellowPerson(name, age) {
            // 调用了一个函数,改变了它的this指向
            Person.call(this, name, age);

            this.color = 'yellow';

            this.talk = function() {
                console.log('我黄种人');
            }
        }


        const y = new YellowPerson('cc', 18);

        console.log(y);

在这里插入图片描述
可以看到,const y = new YellowPerson('cc', 18);参数成功上传,但是原型中的say方法没有成功继承。
构造函数解决了引用类型被所有实例共享的问题,但正是因为解决了这个问题,导致一个很矛盾的问题出现了—函数也是引用类型,也没办法共享了。也就是说,每个实例里面的函数,虽然功能一样,但是却不是一个函数,就相当于我们每实例化一个子类,就复制了一遍函数代码。

3、组合继承

通过上述可以看出,构造函数继承和原型链继承的优缺点是互补的,组合继承就是各取上面2种继承的长处,普通属性使用构造函数继承,函数使用原型链继承。

这样做的缺点就是由于调用了两次父类,所以产生了两份实例,导致原型链上出现了多余的属性 。

function Person(name , age) {  
            this.name = name ; 
            this.age = age ;
            this.type = '人' ; 
        }

        Person.prototype.say = function () {  
            console.log('我是人');
        }



        function YellowPerson(name , age) {  
            Person.call(this , name , age) ;
            this.color = 'yellow' ;
            this.talk = function () {  
                console.log('我黄种人');
            }
        }

        YellowPerson.prototype = new Person('cc' , 18) ;


        const y = new YellowPerson('yy' , 6) ;
        console.log(y);

        console.log(y.type);
        y.say()

        console.log(y.name);

在这里插入图片描述
可以看到子类参数成功上传,原型对象中的方法也成功继承,但是也复制了多余的父类属性,父类构造函数被调用了两次。同时子类实例以及子类原型对象上都会存在name属性。虽然根据原型链机制,并不会访问到原型对象上的同名属性,但总归是不完美。

组合继承的优化
		function Person(name , age) {  
            this.name = name ; 
            this.age = age ;
            this.type = '人' ; 
        }

        Person.prototype.say = function () {  
            console.log('我是人');
        }



        function YellowPerson(name , age) {  
            Person.call(this , name , age)
            this.color = 'yellow' ;
            this.talk = function () {  
                console.log('我黄种人');
            }
        }

      	//将原型对象深复制
        YellowPerson.prototype = {...Person.prototype} ;

        YellowPerson.prototype.aa = 'a' ;


        const y = new YellowPerson('yy' , 19) ;
        console.log(y);


        console.log(Person.prototype);

在这里插入图片描述
没有多余属性,问题解决

4、寄生式继承(中间件继承)

寄生组合继承其实就是在组合继承的基础上,解决了父类构造函数调用两次的问题

		//定义父类
        function Person(name) {
            this.type = 'human';
            this.age = '18';
            this.name = name;
        }

        //在原型中添加
        Person.prototype.say = function() {
            console.log('hi');
        }

        //定义继承方法

        function fn() {}
        fn.prototype = Person.prototype; //地址共享

        function Chinese(name) {
            this.color = 'yellow';
            this.talk = function() {
                console.log('我是中国人');
            }
        }
        Chinese.prototype = new fn();

        const c = new Chinese('csy')
        console.log(c);

创建一个空的构造函数,让这个构造函数的原型对象指向父类的原型对象,也就是说这个构造函数就有了父类的原型对象上的属性和方法;
在这里插入图片描述
没有继承构造函数的属性和方法,只继承了原型方法和原型属性。这就是为什么组合寄生式继承优于普通的组合继承的地方,因为之前已经继承过一次,不再重复继承多一次原型的属性和方法。

下面实现组合寄生式继承:
就在Chinese添加Person.call(this, name)这一行代码就行了
看结果:
在这里插入图片描述
完美继承

5、ES6的继承

ES6提供了class语法糖,同时提供了extends用于实现类的继承。这也是项目开发中推荐使用的方式。

		class Person {
            // 相当于构造函数
            constructor(name , age) {
                this.name = name ; 
                this.age = age ; 
                this.type = '人'
            }
            say() {
                console.log('人');
            }
        }

        // extends 继承
        class YellowPerson extends Person {
            constructor(name , age) {
                //  super 继承父类的属性和方法  ,
                // 这个函数类似于 Person.call(this , name , age) + 原型对象的继承
                super(name , age) ;  // 向父类传参,必须要写这句话,否则会报错  super作为函数调用时表示父类的构造函数,但super内部的this指向的是子类
                this.color = 'yellow' ;
            }
           
            talk() {
                console.log('我是黄种人');
            }

        }

        const y = new YellowPerson('yy' , 20) ;
        console.log(y);

ES6中的类完全可以看作是构造函数的另一种写法;
上面的代码表明,类的数据类型就是函数,类的本身就指向构造函数;
使用时直接对类使用new命令,和构造函数的用法完全一致。
在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值