美文网首页程序员让前端飞我爱编程
【ES6】class的基本使用与继承

【ES6】class的基本使用与继承

作者: 虹猫1992 | 来源:发表于2018-12-09 23:28 被阅读5次

    生成实例对象的传统方法是通过构造函数

    // 先定义一个函数,强行叫它构造函数
    function Point(x, y) {
      this.x = x;
      this.y = y;
    }
    
    // 构造函数的方法都定义在构造函数的原型上
    Point.prototype.toString = function () {
      return '(' + this.x + ', ' + this.y + ')';
    };
    
    // 使用new的方式得到一个实例对象
    var p = new Point(1, 2);
    

    定义类

    ES6 引入了class(类),让javascript的面向对象编程变得更加容易清晰和容易理解。类只是基于原型的面向对象模式的语法糖。

    类实际上是个“特殊的函数”,就像你能够定义的函数表达式和函数声明一样,类语法有两个组成部分:类声明类表达式

    1. 类声明

    类声明是定义类的一种方式,使用关键字class,后面跟上类名,然后就是一对大括号。把类需要定义的方法放在大括号中。

    //定义类
    class Point {
      constructor(x, y) {  // 定义构造方法
        // this关键字代表实例对象
        this.x = x;
        this.y = y;
      }
    
      // 定义在类中的方法不需要添加 function
      toString() {
        return '(' + this.x + ', ' + this.y + ')';
      }
    }
    
    
    //定义类,可以省略 constructor
    class P {
      toString() {
        return '(' + this.x + ', ' + this.y + ')';
      }
    }
    
    1. 类表达式

    类表达式是定义类的另一种形式,类似于函数表达式,把一个函数作为值赋给变量。可以把定义的类赋值给一个变量,这时候变量就为类名。class关键字之后的类名可有可无,如果存在,则只能在类内部使用。

    • 定义类 class后面有类名:
    const People = class StdInfo {
        constructor(){
            console.log(StdInfo);  //可以打印出值,是一个函数
        }
    }
    new People();
    new StdInfo();  //报错,StdInfo is not defined;
    
    
    • 定义类 class后面没有类名:
    const People = class {
        constructor(){
    
        }
    }
    
    new People();
    
    • 立即执行的类:
    let person = new class {
      constructor(name) {
        this.name = name;
      }
    
      sayName() {
        console.log(this.name);
      }
    }('张三');
    
    person.sayName(); // "张三"
    

    不存在变量提升

    定义类不存在变量提升,只能先定义类后使用,跟函数声明有区别的(函数声明会被提升)。

    //-----函数声明-------
    //定义前可以先使用,因为函数声明提升的缘故,调用合法。
    func();
    function func(){}
    
    //-----定义类-------
    new Foo(); // ReferenceError
    class Foo {}
    

    类与构造函数

    1. ES6的类,完全可以看作构造函数的另一种写法。类的数据类型就是函数,类本身就指向构造函数。
    class Point {
      // ...
    }
    
    typeof Point // "function"
    Point === Point.prototype.constructor // true
    
    1. 构造函数的prototype属性,在ES6的“类”上面继续存在;类的所有方法都定义在类的prototype属性上面,Object.assign方法可以一次向类添加多个方法
    class Point {
      constructor(){
        // ...
      }
    }
    
    Object.assign(Point.prototype, {
      toString(){},
      toValue(){}
    });
    

    constructor 方法

    1. constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。
    class Point {
    }
    
    // 等同于
    class Point {
      constructor() {}
    }
    
    1. 一个类中只能有一个constructor函数,定义多个会报错。
    2. constructor默认返回该对象实例(即this),也可以指定返回另外一个对象。
    class Foo {
      constructor() {
        return Object.create(null);
      }
    }
    
    new Foo() instanceof Foo
    // false
    
    1. 在一个构造方法中可以使用super关键字来调用一个父类的构造方法。
    class A {}
    
    class B extends A {
      constructor() {
        super();
      }
    }
    

    类的实例对象

    使用new命令生成类的实例对象。类必须使用new调用,否则会报错。

    class Point {
      // ...
    }
    
    // 报错
    var point = Point(2, 3);
    
    // 正确
    var point = new Point(2, 3);
    

    类的所有实例共享一个原型对象。

    var p1 = new Point(2,3);
    var p2 = new Point(3,2);
    
    p1.__proto__ === p2.__proto__   //true
    

    Class 的静态方法

    1. 类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
    class Foo {
      static classMethod() {
        return 'hello';
      }
    }
    
    Foo.classMethod() // 'hello'
    
    var foo = new Foo();
    foo.classMethod()
    // TypeError: foo.classMethod is not a function
    
    1. 如果静态方法包含this关键字,这个this指的是类,而不是实例。
    class Foo {
      static bar () {
        this.baz();
      }
      // 静态方法可以与非静态方法重名
      static baz () {
        console.log('hello');
      }
     
      baz () {
        console.log('world');
      }
    }
    
    Foo.bar() // hello
    
    1. 父类的静态方法,可以被子类继承。
    class Foo {
      static classMethod() {
        return 'hello';
      }
    }
    
    class Bar extends Foo {
    }
    
    Bar.classMethod() // 'hello'
    
    1. 静态方法也是可以从super对象上调用的。
    class Foo {
      static classMethod() {
        return 'hello';
      }
    }
    
    class Bar extends Foo {
      static classMethod() {
        return super.classMethod() + ', too';
      }
    }
    
    Bar.classMethod() // "hello, too"
    

    new.target 属性

    1. new是从构造函数生成实例对象的命令。在构造函数之中,new.target属性返回new命令作用于的那个构造函数。如果构造函数不是通过new命令调用的,new.target会返回undefined,因此这个属性可以用来确定构造函数是怎么调用的。
    function Person(name) {
      if (new.target !== undefined) {
        this.name = name;
      } else {
        throw new Error('必须使用 new 命令生成实例');
      }
    }
    
    // 另一种写法
    function Person(name) {
      if (new.target === Person) {
        this.name = name;
      } else {
        throw new Error('必须使用 new 命令生成实例');
      }
    }
    
    var person = new Person('张三'); // 正确
    var notAPerson = Person.call(person, '张三');  // 报错
    
    1. Class 内部调用new.target,返回当前 Class
    class Rectangle {
      constructor(length, width) {
        console.log(new.target === Rectangle);
        this.length = length;
        this.width = width;
      }
    }
    
    var obj = new Rectangle(3, 4); // 输出 true
    
    1. 子类继承父类时,new.target会返回子类。
    class Rectangle {
      constructor(length, width) {
        console.log(new.target === Rectangle);
        // ...
      }
    }
    
    class Square extends Rectangle {
      constructor(length) {
        super(length, length);
      }
    }
    
    var obj = new Square(3); // 输出 false
    

    class的继承

    1. Class 可以通过extends关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。使用继承的方式,子类就拥有了父类的方法。
    class Point {
      constructor(x, y) { 
        this.x = x;
        this.y = y;
      }
    
      toString() {
        return '(' + this.x + ', ' + this.y + ')';
      }
    }
    
    class ColorPoint extends Point {
      constructor(x, y, color) {
        super(x, y); // 调用父类的constructor(x, y)
        this.color = color;
      }
    
      toString() {
        return this.color + ' ' + super.toString(); // 调用父类的toString()
      }
    }
    
    1. 如果子类中有constructor构造函数,则必须使用调用super。如果不调用super方法,子类就得不到this对象。
    class Point { /* ... */ }
    
    class ColorPoint extends Point {
      constructor() {
      }
    }
    
    let cp = new ColorPoint(); // ReferenceError
    
    1. 在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。
    class Point {
      constructor(x, y) {
        this.x = x;
        this.y = y;
      }
    }
    
    class ColorPoint extends Point {
      constructor(x, y, color) {
        this.color = color; // ReferenceError
        super(x, y);
        this.color = color; // 正确
      }
    }
    

    super 关键字

    super这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。

    1. super作为函数调用时,代表父类的构造函数。ES6要求,子类的构造函数必须执行一次super函数。
    class A {
      constructor() {
        console.log(new.target.name);
      }
    }
    class B extends A {
      constructor() {
        super();
      }
    }
    new A() // A
    new B() // B
    

    作为函数时,super()只能用在子类的构造函数之中,用在其他地方就会报错。

    1. super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
    class A {
      p() {
        return 2;
      }
    }
    
    class B extends A {
      constructor() {
        super();
        console.log(super.p()); // 2
      }
    }
    
    let b = new B();
    

    上面代码中,子类B当中的super.p(),就是将super当作一个对象使用。这时,super在普通方法之中,指向A.prototype,所以super.p()就相当于A.prototype.p()

    由于super指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的。

    参考文章

    相关文章

      网友评论

        本文标题:【ES6】class的基本使用与继承

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