JavaScript ES6 中实现继承

原型继承关系

1.p1是Person的实例对象
2.obj是Object的实例对象
3.Function/Object/Foo都是Function的实例对象(__proto__ 都等于 Function.prototype)
4.原型对象默认创建时, 隐式原型都是指向Object的显式原型的(Object指向null)
* 推导另外一个结论: Object是Person/Function的父类
  <script>
  var obj = {} // 等价于--->new Object()
  obj.__proto__ // 隐式原型指向的是 Object.prototype(Object的显式原型)
  function foo(){}// 等价于--->new Function()
  console.log(foo.length,foo.name)
  console.log(foo.__proto__)// foo的隐式原型指向的是Function的显式原型Function.prototype

  console.log(foo.__proto__ === Function.prototype) // true
  console.log(Person.__proto__ === Function.prototype) //true
  console.log(foo.__proto__ === Person.__proto__) // true
  console.log(Object.__proto__ === Function.prototype)// true
  console.log(Function.__proto__ === Function.prototype)// true

  // 构造函数
  function Person(){}
  var p1 = new Person()
  var p2 = new Person()
</script>

构造函数的类方法和实例方法

  类方法:是直接添加到对应的构造函数对象上面
  实例方法:是直接添加到原型上面的
  <script>
    function Person(name,age){
      this.name = name
      this.age = age
    }
    Person.totalCounter  = "70亿"
    // 添加到原型上的方法称之为实例方法
    Person.prototype.running = function(){
      console.log(this.name,"跑步!")
    }
    Person.prototype.eating = function(){
      console.log(this.name,"吃东西!")
    }

    //添加到Person本身的方法叫做---->类方法
    var names = ["abc","cba","nab","bba"]
    Person.randomPerson = function(){
      var randomName = names[Math.floor(Math.random()*names.length)]
        return new Person(randomName,Math.floor(Math.random()*100))
    }
    // 实例对象
    // var p1 = new Person("hdc",18)
    // var p2 = new Person("kebo",30)
    // p1.running() 

    // 没有实例对象情况下,能不呢调用函数
    // Person.running()// 这样是没办法调用的
    Person.prototype.running()//这样可以调用但是没有意义


    // 类方法可以调用
  var p = Person.randomPerson()
  console.log(p)
    </script>

认识class定义类

◼ 我们会发现,按照前面的构造函数形式创建 类,不仅仅和编写普通的函数过于相似,而且代码并不容易理解。
    在ES6(ECMAScript2015)新的标准中使用了class关键字来直接定义类;
    但是类本质上依然是前面所讲的构造函数、原型链的语法糖而已;
    所以学好了前面的构造函数、原型链更有利于我们理解类的概念和继承关系;
◼ 那么,如何使用class来定义一个类呢?
    可以使用两种方式来声明类:类声明和类表达式;
    class Person {

    }
    var Student = class {

    }
◼ 注意:类中定义的多个内容不需要使用,进行分割;

案例:
   <script>
    // 编程:高内聚低耦合
    class Person{
      // 1. 类中的构造函数
      // 当我们通过new关键字去调用一个Person类是会默认调用class中的constructor函数方法
      constructor(name,age){
        this.name = name
        this.age = age
      }
      // 2. 类中的实例方法
      // 本质是放在Person.prototype 上
      running(){
        console.log(this.name,"在跑步")
      }
      eating(){
        console.log(this.name,"在吃饭")
      }
    }
    //创建实例对象
    var p1 = new Person("hdc",21)
    // 使用构造函数
    console.log(p1.name,p1.age)
    // 使用实例方法
    p1.running()
    p1.eating()
    // 研究内容
    console.log(Person.prototype === p1.__proto__) // true
    console.log(Person.running)//undefined
    console.log(Person.prototype)//{running: ƒ, eating: ƒ}
  </script>

类的构造函数

◼ 如果我们希望在创建对象的时候给类传递一些参数,这个时候应该如何做呢?
    每个类都可以有一个自己的构造函数(方法),这个方法的名称是固定的constructor;
    当我们通过new操作符,操作一个类的时候会调用这个类的构造函数constructor;
    每个类只能有一个构造函数,如果包含多个构造函数,那么会抛出异常;
◼ 当我们通过new关键字操作类的时候,会调用这个constructor函数,并且执行如下操作:
    1.在内存中创建一个新的对象(空对象);
    2.这个对象内部的[[prototype]]属性会被赋值为该类的prototype属性;
    3.构造函数内部的this,会指向创建出来的新对象;
    4.执行构造函数的内部代码(函数体代码);
    5.如果构造函数没有返回非空对象,则返回创建出来的新对象;
  案例:

    class Person{
      // 1. 类中的构造函数
      // 当我们通过new关键字去调用一个Person类是会默认调用class中的constructor函数方法
      constructor(name,age){
        this.name = name
        this.age = age
      }
    }

类的实例方法

◼ 在上面我们定义的属性都是直接放到了this上,也就意味着它是放到了创建出来的新对象中:
    在前面我们说过对于实例的方法,我们是希望放到原型上的,这样可以被多个实例来共享;
    这个时候我们可以直接在类中定义;
    class Person{
      // 1. 类中的构造函数
      // 当我们通过new关键字去调用一个Person类是会默认调用class中的constructor函数方法
      constructor(name,age){
        this.name = name
        this.age = age
      }
      // 2. 类中的实例方法
      // 本质是放在Person.prototype 上
      running(){
        console.log(this.name,"在跑步")
      }
      eating(){
        console.log(this.name,"在吃饭")
      }
    }

对象访问器方法

  <script>
    // access
    // 针对对象
    // 方式一:描述符方法
    var obj = {
      name:"hdc"
    }
    Object.defineProperty(obj,"name",{
      configurable:true,
      enumerable:true,
      set :function(){

      },
      get:function(){

      }
    })
    // 方式二:直接在对象定义访问器
    // 监听_name什么时候被访问,什么时候设置新的值
    var obj = {
      _name : "hdc",
      // setter方法
      set name(value){
        this._name = value
      },
      // getter 方法
      get name (){
        return this._name
      }
    }
  </script>

类的访问器方法

 ◼ 我们之前讲对象的属性描述符时有讲过对象可以添加setter和getter函数的,那么类也是可以的:
  <script>
    //访问器的编写方式
    class Person{
      constructor(name,age){
        this._name = name
      }
      set name(value){
        console.log("设置name")
        this._name = value
      }
      get name(){
        console.log("获取name")
        return this._name
      }
    }
    var p1 = new Person("hdc",21)
    p1.name = "kebo"
    console.log(p1.name)


    // 2.访问器的应用场景
    class Rectangle{
      constructor(x,y,width,height){
        this.x = x
        this.y = y
        this.width = width
        this.height = height
      }
      get position(){
        return{x:this.x,y:this.y}
      }
      get size(){
        return {width:this.width,height:this.height}
      }
    }

    var rect1 = new Rectangle(10,20,100,200)
    console.log(rect1.position)
    console.log(rect1.size)
  </script>

类的类方法(静态方法)

◼ 静态方法通常用于定义直接使用类来执行的方法,不需要有类的实例,使用static关键字来定义:
<script>
      function Person(){}
      // 实例方法
      Person.prototype.running = function(){}
      // 类方法
      Person.eatting = function(){}
      var p1 = new Person()
      // 实例方法调用
      p1.running()
      // 类方法调用
      Person.eatting()


      // class定义类
      class Student{
        constructor(){}
        // 实例方法
        running(){}
        eatting(){}

        // 类方法(静态方法)
        static studying(){}
      }
      var stu1 = new Student()
      // 调用实例方法
      stu1.running()
      // 调用类方法
      Student.studying()
</script>

ES6类的继承 - extends

◼ 前面我们花了很大的篇幅讨论了在ES5中实现继承的方案,虽然最终实现了相对满意的继承机制,但是过程却依然是非常繁琐的。
◼ 在ES6中新增了使用extends关键字,可以方便的帮助我们实现继承:

super关键字

◼ Class为我们的方法中还提供了super关键字:
    执行 super.method(...) 来调用一个父类方法。
    执行 super(...) 来调用一个父类 constructor构造方法(只能在我们的 constructor 中)
◼ 注意:在子(派生)类的构造函数中使用this或者返回默认对象之前,必须先通过super调用父类的构造函数!
◼ super的使用位置有三个:子类的构造函数方法、实例方法、静态方法;

实现继承案例

    <script>
    // 共同类
    class Person{
      constructor(name,age){
        this.name = name
        this.age = age
      }
      running(){
        console.log(this.name,"跑步")
      }
      eatting(){
        console.log(this.name,"吃东西")
      }
    }
    // 子类1
    class Student extends Person{
      constructor(name,age,sno,score){
        super(name,age)
        this.sno = sno
        this.score = score
      }
      studying(){
        console.log("学习")
      }
    }
    var stu1 = new Student("hdc",21,111,100)
    stu1.running()
    stu1.eatting()
    stu1.studying()
    // 子类2
    class Teacher extends Person{
      constructor(name,age,title){
        super(name,age)
        this.title = title
      }
      teaching(){
        console.log(this.name,"教学")
      }
    }
    var tea1 = new Teacher("hhh",50,"001")
    tea1.running()
    tea1.teaching()
  </script>

super的其他用法

  案例:
  <script>
    class Animal{
      running (){
        console.log("跑")
      }
      eating (){
        console.log("吃")
      }
      // 静态方法(类方法)
      static sleep(){
        console.log(" animal在睡觉")
      }
    }
    class Dog extends Animal{
      // 子类如果对父类继承过来的方法不满意
      // 子类可以重新实现一遍(父类方法的重写)
      running(){
        console.log("dog有四条腿")
        // 如果想重写一部分代码可以调用super
        super.running()
        // console.log("跑")
        // console.log("dog是四条腿在跑")
      }
      static sleep(){
        console.log("趴着")
        super.sleep()
      }
    }
    var dog = new Dog()
    dog.running() //dog有四条腿  跑
    dog.eating() // 吃

    Dog.sleep()// 趴着 animal在睡觉
  </script>

继承内置类

◼ 我们也可以让我们的类继承自内置类,比如Array:
  // 创建一个新的类继承Array进行扩展
  // class HDCArray extends Array{
  //  get lastItem(){
  //     return this[this.length-1]
  //   }
  // }
  // var arr = new HDCArray(10,20,30)
  // console.log(arr)
  // console.log(arr.length)
  // console.log(arr[0])
  // console.log(arr.lastItem)

  // 2.直接对Array进行扩展
  Array.prototype.lastItem = function(){
    return this[this.length-1]
  }
  var arr1 = new Array(10,20,30,40)
  console.log(arr1.lastItem())

类的混入mixin

◼ JavaScript的类只支持单继承:也就是只能有一个父类
    那么在开发中我们我们需要在一个类中添加更多相似的功能时,应该如何来做呢?
    这个时候我们可以使用混入(mixin);
    案例:
      <script>
          // JavaScript 只支持单继承
          // 实现多个继承的方式
          function mixinAnimal(BaseClass){
            return class extends BaseClass{
            running(){
            }
          }
      }
          function mixinRunner(BaseClass){
            return class extends BaseClass{
              flying(){}
            }
          }
          class Bird{
            eating(){}
          }
          var newBird = mixinRunner(mixinAnimal(Bird))
          
          class newBird extends mixinRunner(mixinAnimal(Bird)){

          }
          var bird1 = new newBird()
          // 不支持继承多个父类
          // class Bird extends Animal,Flyer{

          // }
        </script>
posted @ 2024-10-20 01:40  韩德才  阅读(22)  评论(0编辑  收藏  举报