JavaScript中的继承实现(1)

前言:学习过面向对象语言(java、c++)的童鞋都了解面向对象的概念,也确定知道面向对象语言的特征:封装、继承和多态,但JavaScript并不是面向对象,而是基于对象,这些概念咱们没法直接应用到JavaScript的对象机制中,这节咱们主要学习JavaScript开发者是如何曲线救国实现面向对象语言的继承特性java

1.类的概述?c++

  类:类是面向对象语言的基础。类比如模型,好比说动物是一个类别很空泛不具体,拥有不少的特征,可是咱们具体不知道它是会飞、爬、游。app

  对象:那么对象比如是类的一个具体体现,好比狗这个动物就是真实存在的,全部的特征是固定的,会跑、会汪汪叫等。函数

  面向对象:面向对象更多强调的是数据和操做数据的行为是互相关联的,具体的实现就是将数据和关联的行为封装为一个类,基于类完成面向对象语言的开发。性能

  举例:表示单词或短语的字符一般称为字符串。但咱们每每关心的不是数据自己,而是和数据关联的操做。因此就将数据(字符)和行为(计算长度、增删数据等)封装成了统一的String类,全部的字符串就是String类的一个实例。学习

2.何为继承?继承有什么好处?优化

  在现实生活中咱们常会使用继承一词,好比:小鸟继承了妈妈的特征。能够继承妈妈的属性(羽毛、四肢等),也能够继承妈妈的行为(鸣叫、飞等),程序中的继承与其相似。this

  继承:子类在不须要重写父类属性和方法的前提下继承了父类的属性和方法,而且能够直接调用它们spa

  好处:提升了代码的复用性。prototype

3.JavaScript中继承的实现

  在其余语言(java)中,子类继承父类获得只是父类的一个副本,类的继承实质上就是类的复制。可是在JavaScript中只有对象,对象之间不存在类之间复制的功能。那么JavaScript开发者又是如何实现面向对象语言的继承行为的。

  1>构造函数继承

    在子类中经过call/apply方法完成子类对父类的继承。

        // 定义名为Person的构造函数,构造函数只绑定属性name
        function Person(name) {
            this.name = name
            this.getName = function() {
                return this.name;
            }
        }

        // 定义构造函数Bar(),定义属性name和label
        function Student(name, hobby){
            // 经过call改变this的指向,继承Person中的属性name和方法getName()
            Person.call(this, name)
            this.hobby = hobby;
            this.getHobby = function () {
                return this.hobby
            }
        }
        var a = new Student("小明", "basketBall");
        console.log(a.getName());
        console.log(a.getHobby());

  上例中子类Student经过Person.call(this.name)继承了父类Person的name属性和getName()方法,咱们在Student的实例中能够直接调用name属性和getName()方法。

  分析:<1>Person全部实例中的getName()方法行为都是同样的,每实例化一个对象都会定义一个行为相同的getName()方法,但咱们但愿全部实例都共享同一个getName()方法,全部getName()最好的方式是定义在原型上。

  缺点:

    <1>.每一个实例都有相同行为的getName()方法形成内存的浪费。

    <2>.作不到代码的复用,最佳作法getName()方法全部实例共用同一个。

    <3>.形成父类多余属性的继承

  2>原型prototype模式继承

    函数都有prototype属性,在设计构造函数时,咱们将不须要共享的属性和方法,定义在构造函数里面,须要共享的属性和方法,都放在prototype中;

        // 将不共享的name属性定义在Person函数中
        function Person(name) {
            this.name = name
        }
        // 将须要共享的getName方法定义在原型上
        Person.prototype.getName = function(){
            return this.name
        }

        // 定义构造函数Student(),定义属性name和label
        function Student(name, hobby){
            // 经过call改变this的指向,继承Person中的属性name
            Person.call(this, name)
            this.hobby = hobby;
        }
        // Student继承了Person中的方法
        Student.prototype = new Person()
        // 修正Student的构造函数的指向
        Student.prototype.constructor = Student
        // Student定义getHobby的方法
        Student.prototype.getHobby = function() {
            return this.hobby
        }

        var stu = new Student("小明", "basketBall");
        console.log(stu.getName());
        console.log(stu.getHobby());
        

  分析:

  1>.代码中Student.prototype = new Person(),Person实例赋值给Student.prototype,Student.prototype原先的值被删除掉。
  2>.Student.prototype.constructor = Student作了什么呢?每一个Prototype对象都有一个Constructor属性,若是没有Student.prototype = new Person(),

    Student.prototype的constructor是指向Student的,执行这行后constructor指向了Person,更重要的是每一个实例有一个constructor属性默认调用prototype中的constructor属性
     调用Student.prototype = new Person()后,stu.constructor也指向了Person。
        console.log(Student.prototype.constructor === Person)   // true
        console.log(stu.constructor === Person)                 // true
        console.log(stu.constructor === Student.prototype.constructor)  //true

    这就致使了继承链的混乱,由于stu是Student的实例,而不是Person的实例,因此咱们须要手动修正Student.prototype.constructor = Student。

   3>.上例中对于Student.prototype = new Person(),咱们可使用Student.prototype = Person.prototype替换,与第一种相比不须要执行Person函数(提升性能)也不须要建立实例(节省内存),同时缩短了原型链的调用。

    缺点:致使Student.prototype和Person.prototype指向同一个对象引用,修改其中任意一个另外一个就会被影响。好比:Student.prototype.constructor = Student会致使Person.prototype.constructor也是Student。

console.log(Person.prototype.constructor === Student)   //  true

  3>prototype模式改进

    因为Student.prototype = new Person()和Student.prototype = Person.prototype两种方式都存在缺点,这里咱们使用空对象做为中介进行优化。

        /*
            1>F是空函数对于性能的损耗和内存的浪费忽略不计
            2>不会致使child.prototype和parent.prototype指向同一个引用
        */
        function extend (parent, child) {
            function F (){}
            F.prototype = parent.prototype
            child.prototype = new F()
            child.prototype.constructor = child
        }

        // 将不共享的name属性定义在Person函数中
        function Person(name) {
            this.name = name
        }
        // 将须要共享的getName方法
        Person.prototype.getName = function(){
            return this.name
        }

        // 定义构造函数Student(),定义属性name和hobby
        function Student(name, hobby){
            // 经过call改变this的指向,继承Person中的属性name和方法getName()
            Person.call(this, name)
            this.hobby = hobby;
        }

        extend(Person, Student)
        Student.prototype.getHobby = function() {
            return this.hobby
        }

        var stu = new Student("小明", "basketBall");
        console.log(stu.getName());             //小明
        console.log(stu.getHobby());            //basketball
        console.log(Student.prototype.constructor)  //Student
        console.log(Person.prototype.constructor)   //Person

  分析:

    1>上例中咱们封装了extend方法,extend中咱们仍是用空构造函数F做为中介,避免了以前实现方式中的缺点。   

     1>F是空函数对于性能的损耗和内存的浪费忽略不计
      2>不会致使child.prototype和parent.prototype指向同一个引用

    2>对于咱们本身封装的extend方法,咱们其实可使用ES5中提供的Object.create(proto,properties)代替,实现的效果是同样的。

    Student.prototype = Object.create(Person.prototype)

    注意:Object.create()方法的内部实现和咱们本身封装的extend方法相似。

相关文章
相关标签/搜索