深入JS中的面向对象编程补充 - 继承(ES6)与多态

179 阅读5分钟

一、认识class定义类

1.1. 类的概念

  • 在面向对象编程中, 一个  定义了一个对象的特征。类是定义对象属性和方法的模板,是用来绘制具体对象实例的“蓝图”;
    • 在ES6(ECMAScript2015)新的标准中使用了class关键字来直接定义类;
    • 但是类本质上依然是构造函数、原型链的语法糖而已;
    • 它和构造函数的特性其实是一致的;
      class Person {};
      
      var p = new Person();
      
      console.log(Person.prototype); //{}
      console.log(Person.prototype.__proto__); //[Object: null prototype] {}
      console.log(Person.prototype.constructor); //[class Person]
      
      console.log(p.__proto__ === Person.prototype); //true
      

1.2. 类的定义

  • 可以使用两种方式来声明类:类声明和类表达式;
    class Person {};
    var Person = class {};
    

1.3. 类的构造函数

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

二、类的实例方法

  • 我们定义的属性都是直接放到了this上,也就意味着它是放到了创建出来的新对象中:
    • 对于实例的方法,我们是希望放到原型上的,这样可以被多个实例来共享;
    • 这个时候我们可以直接在类中定义;
      class Person {
        constructor(name, age) {
          this.name = name;
          this.age = age;
        }
      
        eating() {
          console.log(this.name + '在吃东西!');
        }
      
        running() {
          console.log(this.name + '在跑步啊!');
        }
      };
      
      var p = new Person('yzh', 18);
      p.eating(); //yzh在吃东西!
      p.running(); //yzh在跑步啊!
      

三、类的访问器方法

  • 对象的属性描述符时有说过对象可以添加setter和getter函数,那么类也是可以的;
    class Person {
      constructor(name, age, height) {
        this.name = name;
        this.age = age;
        this._height = height;
      }
    
      //类的访问器方法
      get height() {
        console.log('拦截访问操作');
        return this._height;
      }
      set height(newVal) {
        console.log('拦截设置操作');
        this._height = newVal;
      }
    };
    
    var p = new Person('yzh', 18, 180);
    
    console.log(p.height); //拦截访问操作 180
    p.height = 1.78; //拦截设置操作
    console.log(p.height); //拦截访问操作 1.78
    

四、类的静态方法

  • 静态方法通常用于定义直接使用类来执行的方法,不需要有类的实例,使用static关键字来定义;
    var names = ['ace', 'sabot', 'luffy'];
    
    class Person {
      constructor(name, age) {
        this.name = name
        this.age = age
      }
    
      static staticProperty = '静态属性';
    
      static staticMethod() {
        return '已调用静态方法';
      }
    
      static randomPerson() {
        var index = Math.floor(Math.random() * names.length); //0-2
        var name = names[index];
        var age = Math.floor(Math.random() * 50);
    
        return new Person(name, age);
      }
    };
    
    var p = new Person('yzh', 18);
    
    console.log(p);
    
    console.log(Person.staticProperty);
    console.log(Person.staticMethod());
    
    //随机10个对象
    for (var i = 0; i < 10; i++) {
      console.log(Person.randomPerson());
    };
    

不能在类的实例上调用静态方法,而应该通过类本身调用;

五、类的继承

5.1. extends关键字

  • ES6中新增了使用extends关键字,可以方便的帮助我们实现继承;
  • extends关键字用于类声明或者类表达式中,以创建一个类,该类是另一个类的子类;
    class Person {
    
    }
    
    class Student extends Person {
    
    }
    

5.2. super关键字

  • 在子(派生)类的构造函数中使用this或者返回默认对象之前,必须先通过super调用父类的构造函数;
  • super的使用位置有三个:子类的构造函数、实例方法、静态方法;
    class Person {
      constructor(name, age) {
        this.name = name;
        this.age = age;
      }
    
      eating() {
        console.log(this.name + '在吃东西!');
      }
    
      running() {
        console.log(this.name + '在跑步啊!');
      }
    
      personMethods() {
        console.log('处理逻辑1');
        console.log('处理逻辑2');
        console.log('处理逻辑3');
      }
    
      static personStaticMethods() {
        console.log('personStaticMethods');
      }
    };
    
    class Student extends Person {
      constructor(name, age, son) {
        super(name, age);
        this.son = son;
      }
    
      // 子类重写父类方法;
      personMethods() {
        super.personMethods(); //调用公共逻辑;
    
        console.log('处理逻辑4');
        console.log('处理逻辑5');
        console.log('处理逻辑6');
      }
    
      // 重写父类静态方法
      static personStaticMethods() {
        super.personStaticMethods();
    
        console.log('Student -> personStaticMethods');
      }
    };
    
    var stu = new Student('yzh', 18, 1804515);
    
    console.log(stu);
    stu.eating();
    stu.running();
    
    stu.personMethods();
    
    Student.personStaticMethods();
    

六、继承内置类

/**
 * 对系统Array类扩展
 * 
 */

class MyArray extends Array {
  firstItem() {
    return this[0]
  }

  lastItem() {
    return this[this.length - 1]
  }
}

var arr = new MyArray(1, 2, 3);

arr.push(4);

console.log(arr);

console.log(arr.firstItem()); //1
console.log(arr.lastItem()); //4

arr:

arr_extends.png

七、类的混入mixin

  • JavaScript的类只支持单继承,也就是只能有一个父类;
  • 在开发中我们需要在一个类中添加更多相似的功能时,这个时候可以使用混入(mixin);
    class Person {
    
    };
    
    function MixinRunner(BaseClass) {
      // 定义类名称并返回
      class NewClass extends BaseClass {
        running() {
          console.log('在跑步!');
        }
      }
    
      return NewClass;
    };
    
    function MixinEater(BaseClass) {
      // 直接返回
      return class extends BaseClass {
        eating() {
          console.log('在吃东西!');
        }
      }
    };
    
    var NewPerson = MixinEater(MixinRunner(Person));
    var nc = new NewPerson();
    
    nc.running(); //在跑步!
    nc.eating(); //在吃东西!
    

八、传统面向对象多态

  • 传统面向对象多态是有三个前提:
    • 1)必须要有继承(是多态的前提);
    • 2)必须有重写(子类重写父类的方法);
    • 3)必须有父类引用指向子类对象(var shape:Shape = new Rectangle());
      // index.ts:
      class Shape {
        getArea() {
      
        }
      };
      
      class Rectangle extends Shape {
        getArea() {
          return 100
        }
      };
      
      class Circle extends Shape {
        getArea() {
          return 200
        }
      };
      
      var r = new Rectangle();
      var c = new Circle();
      
      // 多态:当对不同数据类型执行同一个操作时,如果表现出来的行为(形态)不一样,那么就是多态的体现;
      function calcArea(shape: Shape) {
        console.log(shape.getArea());
      };
      
      calcArea(r);
      calcArea(c);
      
      export {};
      

九、JS面向对象多态

  • 多态:当对不同数据类型执行同一个操作时,如果表现出来的行为(形态)不一样,那么就是多态的体现;
    • 不同数据类型:传入的可能是obj对象可能是p对象;
    • 行为不一样:一个调用obj.getArea(),一个调用p.getArea();
      var obj = {
        name: 'yzh',
        getArea: function() {
          return 100
        }
      };
      
      class Person {
        getArea() {
          return 200
        }
      };
      var p = new Person();
      
      function calcArea(foo) {
        console.log(foo.getArea());
      };
      
      calcArea(obj);
      calcArea(p);
      

因为js比较灵活所以不满足传统多态的前提,也是多态的体现;