美文网首页JS相关前端开发
面向对象(ES5与ES6类的继承解析)

面向对象(ES5与ES6类的继承解析)

作者: Erric_Zhang | 来源:发表于2017-02-09 11:53 被阅读154次

    面向对象的语言都有一个类的概念,通过类可以创建多个具有相同方法和属性的对象,ES6之前并没有类的概念,在ES6中引入类class.

    ES5 面向对象

    创建对象(四种模式简介,此外还有动态原型模式、寄生构造函数模式、稳妥构造函数模式等)

    一、工厂模式


    function createPerson (Name,Age,Job) {

          var man= new Object();

          man.name= Name;

          man.age= Age;

          man.job= Job;

          man.sayName= function () {

                  alert(this.name)

        }

      return  man;

    }

    var personOne=  createPerson ("Erric",26,"Engineer");

    var personTwo=  createPerson ("Lori",26,"teacher");

    优点:解决了多个相似对象的创建问题

    缺点: ①  对象识别问题无法解决(即怎么知道一个对象的类型)

    二、构造函数模式

    function Person (Name,Age,Job) {

          this.name = Name;

          this.age = Age;

          this.job= Job;

          this.sayName= function () {

                  alert(this.name)

          }

    }

    var personOne=  new Person("Erric",26,"Engineer");

    var personTwo=  new Person("Lori",26,"teacher");

    注一: 若不使用new操作符直接调用函数,那么其属性和方法都会被添加到window对象里面(因为在全局作用域调用一个方法时,this总是指向window对象)

    如: Person("Erric",26,"Enginee")

            window.sayName()  //  弹出 "Erric"

              window.name            //  "Erric"

              window.age              //  26

    注二: new 操作符实际上进行了以下操作

              ① 创建一个新的对象

              ② 将构造函数的作用域赋给新对象(this指向了这个新的对象)

              ③ 执行构造函数中的代码(为这个新对象添加属性)

              ④ 返回这个新的对象

    优点:① 不用显式的创建对象

                ② 将属性和方法赋给了this对象

                ③ 没有return语句

    缺点:①  每个方法都要在每个实例上重新创建一遍(personOne和personTwo中的sayName方法不是同一个方法,每个函数都是一个对象,故每  定义了一个函数就实例化了一个对象)。

                此问题也可以通过将方法单独抽出来解决(但是方法一多,都移到全局的话封装性就无从谈起),如下:

                function Person (Name,Age,Job) {

                        this.name = Name;

                          this.age = Age;

                          this.job= Job;

                          this.sayName= sayName

                }

                function sayName() {

                        alert(this.name)

                  }

                var personOne=  new Person("Erric",26,"Engineer");

                var personTwo=  new Person("Lori",26,"teacher");

                ② 若是将公共的sayName方法移到全局,那么又没有封装性可言了。


    三、原型模式

    function Person () {

    }

    Person.prototype.name= "Erric"

    Person.prototype.age= "28"

    Person.prototype.job= "Job"

    Person.prototype.sayName= function () {

            alert(this.sayName)

    }

    优点:①  解决了函数共用的问题,不用每个实例都创建一遍方法。

    缺点:①  不能传参

                ② 如果实例中修改了原型中的属性(引用类型)或方法,那么这个属性或方法会被彻底的修改,而影响到其他实例。


    四、构造函数+原型组合模式

    function Person (Name,Age,Job) {

              this.name= Name

              this.age= Age

              this.job= Job

    }

    Person.prototype.sayName= function () {

              alert(this.name)

    }

    // 上面往原型上添加属性和方法的也可如下写,但是此时原型的constructor不指向Person构造函数,而是指向Object,因为Person.prototype就像一个新的对象实例,它的__proto__指向Object原型。

    //  Person.prototype= {

              constructor: Person,            // 重新再实例中定义constructor的指向,覆盖Object原型中的constructor指向

              sayName: function () {

                      alert(this.name)

              }

    }

    var personOne=  new Person("Erric",26,"Engineer");

    var personTwo=  new Person("Lori",26,"teacher");


    原型对象的理解(重要)

    1.首先得明白以下三点:

    ① 每个函数(含构造函数)都有一个prototype属性,指向Person原型

    ② 每个实例都有一个__proto__属性,也指向Person原型

    ③ 每个原型都有一个constructor属性,指向其对应的构造函数

    构造函数、实例、原型三者关系如下图:

    2.万物皆对象,说明原型链的最开始点都是Object,所以任何一个引用类型的 instanceof Object都会返回true。


    类的继承(两种方式)

    一、原型链继承

            对于什么是原型链?

            每个构造函数都有一个原型对象,原型对象的constructor指向这个构造函数本身,而实例的__proto__属性又指向原型对象。这个假设一个实例的__proto__内部指针指向其原型,而它的原型又是另一个类型的实例,那么它的原型又将指向另一个原型,另一个原型也包含一个指向它的构造函数的指针,假设另一个原型又是另一个类型的实例,这样层层递进,就构成了实例与原型的链条,这就是原型链的基本概念。

    实现原型链的继承方式基本如下:

    function Father () {

          this.appearance = "beautiful"

    }

    Father.prototype.sayHappy = function () {

            alert("快乐")

    }

    function Child () {

              this.name= "Jhon"

    }

    Child.prototype= new Father()        //  继承了父类的方法和属性

    Child.prototype.addArr= [1,2,3,4,5]

    var child= new Child()
    child.sayHappy()          //  弹出“快乐”
    child.appearance        //  "beautiful"

    child.addArr                      //  [1,2,3,4,5]

    原型链继承的缺点:①  不能传参  ② 若原型上的方法时引用类型的话,不小心被修改了的话会影响其他实例。


    二、借助构造函数继承(利用calll和apply改变this指针)

    基本思路:在子类型构造函数的内部调用超类型的构造函数。

    function Father (Hobby){

          this.hobby= Hobby

    }

    Father.prototype.sayHappy = function () {

          alert("快乐")

    }

    function Child () {

          this.name= "Jhon"

          Father.call(this,"Play Games")          //  或者Father.apply(this,["Play Games"]),继承了Father的属性和方法

    }

    var child =  new Child()
    child.sayHappy                // 没有反应,原型上的方法和属性不会继承
    child.hobby                      //  "Play Games"

    借助构造函数继承的缺点:①  方法都在构造函数中定义,函数的复用无从谈起    ②  超类中的方法对子类不可见。


    三、组合继承(也叫经典继承,将原型链和借助构造函数继承相结合)

    思路:1.原型链实现对原型属性和方法的继承;

                2.构造函数实现对实例属性的继承,且调用基类的构造函数;

    function Father(Hobby) {

              this.hobby= Hobby;

              this.exGF = ['cuihua', 'erya']

    }

    Father.prototype.sayHappy = function () {

              alert("快乐")

    }

    function Child () {

              this.name= "Jhon"

              Father.call(this,"Play Games")          //  或者Father.apply(this,["Play Games"]),继承了Father的属性和方法

    }

    Child.prototype= new Father()

    Student.prototype.sayName= function () {

              alert(this.name);

    }

    var liHua= new Child()

    liHua.sayHappy()

    liHua.sayName()


    检测对象属性的两种方法:

    object.hasOwnProperty(属性名),这个方法检测的是对象实例的属性(若是返回true),不能检测原型上的属性。

    in操作符,检测对象所有的属性,包含原型和实例上的额,有的话就返回true.


    判断一个原型是否在某个实例的原型链上:

    Person.prototype.isPropotypeOf(personOne)    //  true

    Object.prototype.isPropotypeOf(personOne)      //  true

    判断一个构造函数是否在实例的原型链中出现过:

    personOne instanceof Person                //  true

    personOne instanceof Object                //  true


    ES6 面向对象

    ES6中引入了Class(类)这个概念,通过关键字class可以创建一个类。类的数据类型就是函数,类的所有方法都定义在prototype属性上。

    class Person () {
            constructor (x,y) {
                  this.name= x
                  this.age= y
            }
            sayName () {
                    alert("快乐")
            }
    }
    var liHua= new Person("张俊泽",26)

    注: 可以理解为constuctor中的属性和方法为ES5中的构造函数部分,和constructor同级的是ES5中原型上的方法和属性。


    ES6的继承通过extends关键字实现

    class Father(){}
    class Child extends Father {
            constructor(x,y,color){
                      super(x,y)
                      this.color= color
            }
            toString() {
                    retunr "世界和平!"
            }
    }

    上面代码中,constructor方法和toString方法之中,都出现了super关键字,它在这里表示父类的构造函数,用来新建父类的this对象。

    子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。


    类的prototype和__proto__属性

    Class作为构造函数的语法唐,同时有prototype和__proto__属性,因此存在两条继承链:

    ①  子类的__proto__,表示构造函数的继承,总是指向父类

    ②  子类的prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。

    class Father {

    }

    class Child extends Father{

              constructor () {

                      super()

              }

    }

    var childOne= new Child()

    Child.__proto__ ==  Father        //  true

    childOne.__proto__ ==  Child.prototype        //  true

    Child.prototype.__proto__ ==  Fahter.prototype            //  true

    相关文章

      网友评论

        本文标题:面向对象(ES5与ES6类的继承解析)

        本文链接:https://www.haomeiwen.com/subject/cxzpittx.html