ES 6 Classクラス


ES 6(ES 2015)から、JSはクラス(Class)の概念を提出し、JSの中のクラスはJSの既存の、原型に基づく継承の一種の文法包装(文法糖)にすぎず、それは私たちに合理的で簡明な文法で継承を実現させることができる.

クラスの定義


ES 6のクラスは実際には1つの関数であり、関数の定義方式には関数宣言と関数式の2つの方式があるように、クラスの定義にも2つの方式があり、それぞれ:
  • クラス宣言
  • クラス式
  • クラス宣言


    クラス宣言はクラスを定義する方法で、classキーを使用してクラス名を付けると、クラスを定義できます.次のようになります.
    class Foo {
        constructor() {
            // ..
        }
    }

    変数リフトは存在しません(hoist)


    クラス宣言と関数宣言の違いは、関数宣言には変数の昇格現象があり、クラス宣言はありません.すなわち、クラスは、ReferenceErrorの例外を投げ出す前に宣言してから使用する必要があります.
    var foo = new Foo(); // Uncaught ReferenceError: Foo is not defined(...)
    class Foo {
        // ...
    }

    この規定の原因はクラスの継承に関係しており、子クラスが親クラスの後に定義されることを保証しなければならない.
    let Foo = class {};
    
    class Bar extends Foo {
    }

    上記のコードは、class BarFooを継承する場合、Fooが定義されているため、エラーは発生しません.ただし、Class昇格が存在する場合、上記のコードはエラーとなります.Class Barはコードヘッダに昇格し、式式Fooは昇格しないため、Class BarFooを継承する場合、Fooはまだ定義されていません.

    クラス式


    クラス式は、関数式のようにクラス名があってもなくてもよいクラスを定義する別の方法です.定義されたクラス名の場合、そのクラス名はクラスの内部のみにアクセスできます.
    //    
    const MyClass = class {};
    
    //    :    
    const MyClass = class Me {
        getClassName() {
            return Me.name;
        }
    };

    上記の方式2でクラスを定義するとともにクラス名が与えられるが,この場合,Meクラス名はClassの内部コードでのみ利用可能であり,現在のクラスを指す.MyClassのname属性値は与えられたクラス名である.
    let my = new MyClass();
    my.getClassName(); // Me
    Me.name; // Uncaught ReferenceError: Me is not defined(…)
    MyClass.name; // Me

    クラス式を使用すると、すぐに実行されるClassを書くことができます.次のようになります.
    let person = new class {
        constructor(name) {
            this.name = name;
        }
    
        sayName() {
            console.log(this.name);
        }
    }('Zhang San');
    
    person.sayName(); // Zhang San

    クラスとメソッド定義


    クラスのメンバーは、1対のカッコ内の{}を定義する必要があります.カッコ内のコードのカッコ自体がクラス体を構成します.クラスメンバーには、クラスコンストラクタとクラスメソッド(静的メソッドとインスタンスメソッドを含む)が含まれます.

    厳格モード


    クラス内のコードは、デフォルトの「use strict」である厳格なモードで強制的に実行されます.将来のすべてのコードがモジュール内で実行されることを考慮すると、ES 6は実際に言語全体を厳格なモードにアップグレードした.

    コンストラクタほうしき

    constructorメソッドは、静的メソッドでもインスタンスメソッドでもなく、インスタンス化時にのみ呼び出される特殊なクラスメソッドです.クラスにはconstructorというメソッドしかありません.そうしないと、SyntaxError異常が放出されます.constructorメソッドが定義されていない場合、このメソッドはデフォルトで追加されます.すなわち、定義が表示されているかどうかにかかわらず、どのクラスにもconstructorメソッドがあります.
    サブクラスはconstructorメソッドでsuperメソッドを呼び出す必要があります.そうしないと、インスタンスを新規作成するときにエラーが発生します.子クラスは独自のthisオブジェクトを持たず、親クラスのthisオブジェクトを継承して加工するため、superメソッドを呼び出さなければ、子クラスはthisオブジェクトを得ることができない.
    class Point {}
    
    class ColorPoint extends Point {
        constructor() {}
    }
    
    let cp = new ColorPoint(); // ReferenceError

    上記のコードでは、ColorPointは親Pointを継承していますが、その構造関数はsuperメソッドを呼び出さず、新しいインスタンス・タイムズが間違っています.

    プロトタイプメソッド


    クラスのメソッドを定義する場合、メソッド名の前にfunctionのキーワードを付ける必要はありません.また、メソッド間をカンマで区切る必要はなく、加算するとエラーが表示されます.
    class Bar {
        constructor() {}
    
        doStuff() {}
    
        toString() {}
    
        toValue() {}
    }

    クラスのすべてのメソッドは、クラスのprototypeプロパティに定義されています.上記の書き方は、次のようになります.
    Bar.prototype = {
        doStuff() {},
        toString() {},
        toValue() {}
    };

    したがって,クラスのインスタンスでメソッドを呼び出すことは,実際にはプロトタイプを呼び出すメソッドである.
    class B {}
    let b = new B();
    
    b.constructor === B.prototype.constructor; // true

    上記のコードでは、bはBクラスの例であり、そのconstructorメソッドはBクラスプロトタイプのconstructorメソッドである.クラスのメソッドはいずれもprototypeに定義されているため、クラスの新しいメソッドはprototypeオブジェクトに追加できます.Object.assignメソッドは、クラスに複数のメソッドを一度に追加するのに便利である.
    class Point {
        constructor() {
            // ...
        }
    }
    
    Object.assign(Point.prototype, {
        toString() {},
        toValue() {}
    });

    また,クラスの内部のすべての定義方法は,枚挙にいとまがない(non−enumerable).
    class Point {
        constructor(x, y) {
            // ...
        }
    
        toString() {
            return '(' + x + ', ' + y + ')';
        }
    }
    
    Object.keys(Point.prototype); // []
    Object.getOwnPropertyNames(Point.prototype); // ["constructor", "toString"]
    Object.getOwnPropertyDescriptor(Point, 'toString');
    // Object {writable: true, enumerable: false, configurable: true}

    スタティツクメソッド

    staticキーワードは、クラスの静的メソッドを定義するために使用されます.静的メソッドとは、クラスをインスタンス化する必要がなく、クラス名を使用して直接アクセスできるメソッドです.静的メソッドは、ツール関数としてよく使用されます.
    class Point {
        constructor(x, y) {
            this.x = x;
            this.y = y;
        }
    
        static distance(a, b) {
            const dx = a.x - b.x;
            const dy = a.y - b.y;
    
            return Math.sqrt(dx*dx + dy*dy);
        }
    }
    
    const p1 = new Point(5, 5);
    const p2 = new Point(10, 10);
    
    console.log(Point.distance(p1, p2));

    静的メソッドはインスタンスに継承されず、クラス名によって直接呼び出されます.ただし、親の静的メソッドは、クラスに継承されます.
    class Foo {
      static classMethod() {
        return 'hello';
      }
    }
    
    class Bar extends Foo {
    }
    
    Bar.classMethod(); // "hello"

    静的方法は、superキーワードで呼び出すこともできる.
    class Foo {
      static classMethod() {
        return 'hello';
      }
    }
    
    class Bar extends Foo {
      static classMethod() {
        return super.classMethod() + ', too';
      }
    }
    
    Bar.classMethod(); // "hello too"

    extendsキーワード

    extendsキーワードは、クラス間の継承を実現するために使用される.子が親を継承すると、親のすべての属性とメソッドが継承されます.extendsの後には1つの親しか付いていません.

    superキーワード

    superキーワードは、その親のコンストラクタまたはメソッドを呼び出すために使用することができる.
    class Cat { 
      constructor(name) {
        this.name = name;
      }
    
      speak() {
        console.log(this.name + ' makes a noise.');
      }
    }
    
    class Lion extends Cat {
      speak() {
        super.speak();
        console.log(this.name + ' roars.');
      }
    }

    クラスのGetterとSetterメソッド


    ES 5と同様に、クラス内でgetおよびsetのキーワードを使用して、ある属性に値の取り方および付与方法を設定することができる.
    class Foo {
        constructor() {}
    
        get prop() {
            return 'getter';
        }
    
        set prop(val) {
            console.log('setter: ' + val);
        }
    }
    
    let foo = new Foo();
    
    foo.prop = 1;
    // setter: 1
    
    foo.prop;
    // "getter"

    上記のコードでは、prop属性に対応する付与方法と取値方法があるため、付与動作と読み出し動作がカスタマイズされている.値の格納方法と値の取得方法は、プロパティのdescriptorオブジェクトに設定されます.
    var descriptor = Object.getOwnPropertyDescriptor(Foo.prototype, 'prop');
    
    "get" in descriptor // true
    "set" in descriptor // true

    上記のコードでは、メモリ値および値取り方法は、ES 5と一致するprop属性の記述オブジェクトに定義されている.

    クラスのGeneratorメソッド


    クラスのメソッド名にアスタリスク(*)が付けられている場合、このメソッドはGenerator関数であることを示します.
    class Foo {
      constructor(...args) {
        this.args = args;
      }
      * [Symbol.iterator]() {
        for (let arg of this.args) {
          yield arg;
        }
      }
    }
    
    for (let x of new Foo('hello', 'world')) {
      console.log(x);
    }
    // hello
    // world

    上のコードでは、FooクラスのSymbol.iteratorメソッドの前にアスタリスクがあり、メソッドがGenerator関数であることを示します.Symbol.iteratorメソッドはFooクラスのデフォルトの遍歴器を返し、for...ofサイクルでこの遍歴器が自動的に呼び出されます.