继承再学习

来源:互联网 发布:python jieba分词 编辑:程序博客网 时间:2024/05/04 06:33
  • 对于继承,是面试经常会面到的问题,而且也是平时写代码的时候常用到的地方。之前也写过继承的文章 ,再次翻看,之前写的是啥呀。学习这个东西真的很奇怪,它永远是那个只要你付出就会有汇报的家伙。
  • 最近面试也面了很多,几乎都会问到,如何理解原型链,如何理解继承?我的回答基本是一样的:
    原型链就是让一个对象的原型等于另一个对象的实例。
    继承比较好的方法就是用借用构造函数来继承属性,用原型链来继承方法。

个人感觉这个回答基本还可以,很多面试官也没有继续深问。因为本来嘛,这样实现继承既避免了所有实例共享属性,又能够使所有实例共享方法,不必每次创建实例的时候都创建一个方法。

然而,把继承理解到这个份上,还是差太多!因为,组合继承也是存在缺点的。下面就来仔细说一下继承。

1、原型链

先来回顾构造函数、原型和实例之间的关系,用一个图来说明:
这里写图片描述

构造函数中有一个 prototype的指针,指向其原型对象,原型对象中有一个constructor指针指向构造函数,实例中有一个[[prototype]]指针指向原型对象。

那么,如果让Person对象的原型等于另一个对象的实例,那么这个原型又会指向另一个对象的原型。假设,另一个对象的原型又是另一个对象的实例,那么上述关系依然成立。这就是原型链。
这里写图片描述
这样看起来似乎不太好看,重新画一下:
这里写图片描述

这样看起来就比较清楚,是一个链条,不停的继承,直到Object。

但是原型链来实现继承是有问题的,问题在于属性共享,因此很少单独使用。
注:如果你使用原型继承,可以打印一下,子类.prototype.constructor = 父类构造函数。

2、组合式继承

由于原型链实现继承会造成属性共享,所以就想通过借用构造函数来实现属性继承,原型链实现方法继承。

        function Person (name,age) {            this.name    = name;            this.age     = age;            this.friends = ['Jay','Hanna','Vicky'];        }        Person.prototype.sayHello = function () {            console.log(this.name + 'Hello!');        }        function Student (name,age,sex) {            Person.call(this,name,age);//借用父类构造函数            this.sex = sex;        }        Student.prototype = new Person();//原型继承        Student.prototype.constructor = Student;//这句很重要!!!        Student.prototype.isGirl = function () { //在子类原型上定义方法            var isGirl;            if(this.sex !== "female"){                isGirl = false;            } else {                isGirl = true;            }            console.log("isGirl:" + isGirl);        };        var student1 = new Student ("xiaoming",22,"male");        student1.friends.push("David");        console.log(student1.friends + "  " );        student1.sayHello();        var student2 = new Student("xiaohong",23,"female");        console.log(student2.friends);        student2.isGirl();

这里写图片描述

注意,注意,注意:

这里还要再强调一点,上面注释中有一句,这句很重要。
Student.prototype = new Person()之后,接着来了一句 Student.prototpye.constructor = Student 。之前我一直没太明白为什么要这么加一句,今天看了阮一峰的博客,弄明白了。

  • 任何一个Prototype对象都有一个constructor指针,指向它的构造函数;
  • 每个实例中也会有一个constructor指针,这个指针默认调用Prototype对象的constructor属性。

当替换了子类的原型之后,即 Student.prototype = new Person()之后,Student.prototype.constructor 就指向了Person(),Student的实例的constructor也指向了Person(),这就出现问题了。因为:
这造成了继承链的紊乱,因为Student的实例是由Student构造函数创建的,现在其constructor属性却指向了Person

为了避免这一现象,就必须在替换prototype对象之后,为新的prototype对象加上constructor属性,使其指向原来的构造函数。

3、组合式继承的缺点

最大的问题: 无论什么情况下,都会调用两次超类型构造函数:

  • 一次是在创建子类原型的时候;
  • 一次是在子类型构造函数内部

测试一下,还是刚才的代码,在父类构造函数里加一句console.log

        function Person (name,age) {            this.name    = name;            this.age     = age;            this.friends = ['Jay','Hanna','Vicky'];            console.log(this);        }        Person.prototype.sayHello = function () {            console.log(this.name + 'Hello!');        }        function Student (name,age,sex) {            Person.call(this,name,age);//第二次调用父类构造函数            this.sex = sex;        }        Student.prototype = new Person();//第一次调用父类构造函数        Student.prototype.constructor = Student; //这句很重要        Student.prototype.isGirl = function () {            var isGirl;            if(this.sex !== "female"){                isGirl = false;            } else {                isGirl = true;            }            console.log("isGirl:" + isGirl);        };        var student1 = new Student ("xiaoming",22,"male");//第二次调用父类构造函数        student1.friends.push("David");        console.log(student1.friends + "    " );        student1.sayHello()

这里写图片描述

其实这里还有一个问题,为什么调用两次父类构造函数就不好呢???
个人初步理解,因为这样做很多余。只需要调用一次就可以达到目的,为何要调用两次呢?
所以,为了避免这个问题,我们不必为了指定子类型的原型而调用一次父类的构造函数,只需要超类型的一个副本即可。

//组合继承的缺点:会两次调用父类的构造函数,一次是创建子类原型的时候,一次是调用子类子类构造函数的时候。改进方法:使用寄生组合式继承,不必为了指定子类型的原型而调用父类的构造函数,我们所需要的无非是超类型原型的一个副本而已。        function object(o){            function F () {}            F.prototype = o;            return new F();        }//借助原型可以基于已有对象创建新对象        // 寄生组合式继承        function inheritPrototype (child,parent) {            var prototype         = object(parent.prototype); //创建父类原型的副本            prototype.constructor = child;            child.prototype       = prototype;        }        function Singer (name,age,country) {            Person.call(this,name,age);            this.country = country;        }        inheritPrototype(Singer,Person);        Singer.prototype.fromWhere = function () {            console.log("The singer is from:" + this.country);        }        var singer1 = new Singer("Jay",37,"China");        singer1.sayHello();        singer1.fromWhere();

面试的时候有面试官问,你这样创建一个副本不是浪费内存吗,还有别的方法吗?阮一峰大大的博客里有介绍,可以自行去看 ,地址:
http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_inheritance.html

这里,最后又补充一点,这个问题困扰了我好久好久,就是,继承的时候,为什么要让:
子类的原型=父类的实例,而不是子类的原型=父类的原型

看下面的例子:

        //父类        function Father() {}        Father.prototype.type = "Person";        //子类        function Son(name) {            this.name = name;        }        console.log(Father.prototype.constructor);//打印父类的构造函数        Son.prototype = Father.prototype;//让子类的原型=父类的原型,来实现继承        Son.prototype.constructor = Son;        var son = new Son("Liming");//子类实例        console.log(son.type);//Person,子类继承了父类的属性        console.log(Father.prototype.constructor);//再次打印父类的构造函数

看下结果:
这里写图片描述
第二次再打印父类原型的constructor属性时,已经变成了子类的构造函数!这是因为,
Son.prototype = Father.prototype之后,Son.prototype 和Father.prototype现在指向了同一个对象,那么任何对Son.prototype的修改,都会反映到Father.prototype上,所以,一般不会让子类的原型直接继承父类的原型。

2 0
原创粉丝点击