1. ホーム
  2. javascript

[解決済み] ES2015 (ES6) の `class` 構文にはどのような利点があるのでしょうか?

2023-02-05 15:05:08

質問

ES6クラスについて、いろいろと質問させてください。

を使用する利点は何ですか? class 構文を使用する利点は何ですか?public/private/staticがES7に含まれると読みましたが、それが理由でしょうか?

さらに class は別の種類の OOP なのでしょうか、それとも JavaScript のプロトタイプ的な継承なのでしょうか?を使って修正することはできるのでしょうか? .prototype ? それとも、同じオブジェクトでありながら、2つの異なる方法で宣言しているのでしょうか?

スピード面でのメリットはあるのでしょうか?ビッグアプリのような大きなアプリケーションであれば、メンテナンス/理解がしやすいのかも?

どのように解決するのか?

新しい class の構文は、完全ではないものの、ほとんどが構文上の糖分です (しかし、その good のようなものです)。コンストラクタ関数と、コンストラクタ関数が生成するオブジェクトのプロトタイプとして割り当てるオブジェクトの記述を著しく簡素化します。特に、ES5 構文ではエラーが発生しがちだった継承階層の設定を簡素化します。しかし、古い方法とは異なり class 構文でも super.example() をスーパーコールに使用できるようになりました。 プロパティ宣言 , プライベートフィールド そして プライベートメソッド (を含む)。 静的メソッド ).

(たまに class 構文を使わなければならないと言う人もいます。 Error または Array [というように、ES5では適切にサブクラス化することができませんでした]。そんなことはありません、ES2015の別の機能を使えばいいのです。 Reflect.construct [ スペック , MDN を使いたくない場合は class の構文を使用します¹)。

さらに、は class は別の種類の OOP なのでしょうか、それとも JavaScript のプロトタイプの継承なのでしょうか?

今までと同じプロトタイプの継承で、よりクリーンで便利、そしてエラーが起こりにくい構文になっています。 もし を使うのが好きなら、コンストラクタ関数 ( new Foo など)を使うのが好きで、さらにいくつかの機能が追加されています。

を使って修正することは可能ですか? .prototype ?

はい、あなたはまだ prototype オブジェクトを修正することができます。例:これは完全に合法です。

class Foo {
    constructor(name) {
        this.name = name;
    }
    
    test1() {
        console.log("test1: name = " + this.name);
    }
}
Foo.prototype.test2 = function() {
    console.log("test2: name = " + this.name);
};

速度面でのメリットはありますか?

具体的なイディオムを用意することで、推測ですが 可能 を提供することで、エンジンはより良い最適化を行うことができるかもしれません。しかし、エンジンはすでに最適化するのが非常にうまいので、大きな違いはないと思います。特に class 構文について特に言えることは、もしあなたが プロパティ宣言 の数を最小限にすることができます。 形状の変更 を最小化することができ、コードの解釈と後のコンパイルを少し速くすることができます。しかし、繰り返しになりますが、それは大きなものではありません。

ES2015 (ES6) はどのような利点があるのでしょうか? class の構文がもたらす利点は何でしょうか?

簡単に説明します。そもそもコンストラクタ関数を使用しないのであれば Object.create などとする。 class は、あなたにとって有用ではありません。

もしコンストラクタ関数を使うのであれば、いくつかの利点があります。 class :

  • 構文がよりシンプルになり、エラーが起こりにくくなりました。

  • それは 大いに 新しい構文で継承階層を設定する方が、古い構文よりずっと簡単です (そしてまた、エラーが起こりにくいです)。

  • class を使用しないというよくある間違いからあなたを守ります。 new をコンストラクタ関数と一緒に使用することに失敗する一般的なエラーを防御します(コンストラクタが例外を投げるようにすることで)。

  • 親プロトタイプのバージョンのメソッドを呼び出すことは、古い構文よりも新しい構文ではるかに簡単です ( super.method() の代わりに ParentConstructor.prototype.method.call(this) または Object.getPrototypeOf(Object.getPrototypeOf(this)).method.call(this) ).

  • プロパティ宣言は、作成されるインスタンスの形状をコンストラクタのロジックから分離して明確にすることができます。

  • プライベートフィールドとメソッド(インスタンスと静的の両方)を class 構文で使用でき、ES5 構文では使用できません。

以下は、階層の構文比較(プライベートメンバーなし)です。

// ***ES2015+**
class Person {
    constructor(first, last) {
        this.first = first;
        this.last = last;
    }

    personMethod() {
        // ...
    }
}

class Employee extends Person {
    constructor(first, last, position) {
        super(first, last);
        this.position = position;
    }

    employeeMethod() {
        // ...
    }
}

class Manager extends Employee {
    constructor(first, last, position, department) {
        super(first, last, position);
        this.department = department;
    }

    personMethod() {
        const result = super.personMethod();
        // ...use `result` for something...
        return result;
    }

    managerMethod() {
        // ...
    }
}

// ***ES2015+**
class Person {
    constructor(first, last) {
        this.first = first;
        this.last = last;
    }

    personMethod() {
        return `Result from personMethod: this.first = ${this.first}, this.last = ${this.last}`;
    }
}

class Employee extends Person {
    constructor(first, last, position) {
        super(first, last);
        this.position = position;
    }

    personMethod() {
        const result = super.personMethod();
        return result + `, this.position = ${this.position}`;
    }

    employeeMethod() {
        // ...
    }
}

class Manager extends Employee {
    constructor(first, last, position, department) {
        super(first, last, position);
        this.department = department;
    }

    personMethod() {
        const result = super.personMethod();
        return result + `, this.department = ${this.department}`;
    }

    managerMethod() {
        // ...
    }
}

const m = new Manager("Joe", "Bloggs", "Special Projects Manager", "Covert Ops");
console.log(m.personMethod());

vs.

// **ES5**
var Person = function(first, last) {
    if (!(this instanceof Person)) {
        throw new Error("Person is a constructor function, use new with it");
    }
    this.first = first;
    this.last = last;
};

Person.prototype.personMethod = function() {
    // ...
};

var Employee = function(first, last, position) {
    if (!(this instanceof Employee)) {
        throw new Error("Employee is a constructor function, use new with it");
    }
    Person.call(this, first, last);
    this.position = position;
};
Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee;
Employee.prototype.employeeMethod = function() {
    // ...
};

var Manager = function(first, last, position, department) {
    if (!(this instanceof Manager)) {
        throw new Error("Manager is a constructor function, use new with it");
    }
    Employee.call(this, first, last, position);
    this.department = department;
};
Manager.prototype = Object.create(Employee.prototype);
Manager.prototype.constructor = Manager;
Manager.prototype.personMethod = function() {
    var result = Employee.prototype.personMethod.call(this);
    // ...use `result` for something...
    return result;
};
Manager.prototype.managerMethod = function() {
    // ...
};

ライブの例です。

// **ES5**
var Person = function(first, last) {
    if (!(this instanceof Person)) {
        throw new Error("Person is a constructor function, use new with it");
    }
    this.first = first;
    this.last = last;
};

Person.prototype.personMethod = function() {
    return "Result from personMethod: this.first = " + this.first + ", this.last = " + this.last;
};

var Employee = function(first, last, position) {
    if (!(this instanceof Employee)) {
        throw new Error("Employee is a constructor function, use new with it");
    }
    Person.call(this, first, last);
    this.position = position;
};
Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee;
Employee.prototype.personMethod = function() {
    var result = Person.prototype.personMethod.call(this);
    return result + ", this.position = " + this.position;
};
Employee.prototype.employeeMethod = function() {
    // ...
};

var Manager = function(first, last, position, department) {
    if (!(this instanceof Manager)) {
        throw new Error("Manager is a constructor function, use new with it");
    }
    Employee.call(this, first, last, position);
    this.department = department;
};
Manager.prototype = Object.create(Employee.prototype);
Manager.prototype.constructor = Manager;
Manager.prototype.personMethod = function() {
    var result = Employee.prototype.personMethod.call(this);
    return result + ", this.department = " + this.department;
};
Manager.prototype.managerMethod = function() {
    // ...
};        

var m = new Manager("Joe", "Bloggs", "Special Projects Manager", "Covert Ops");
console.log(m.personMethod());

見ての通り、そこには繰り返しの多い冗長なものがあり、間違えやすく、再入力するのも退屈です(私は昔、このためにスクリプトを使っていました、以前は class が登場する前は、このためにスクリプトを使っていました)。

注意しなければならないのは、ES2015のコードでは Person 関数のプロトタイプが Employee 関数のプロトタイプですが、ES5のコードではそうではありません。ES5では、そのような方法はありません。すべての関数で Function.prototype をプロトタイプとして使用します。環境によっては、サポートされている __proto__ 擬似プロパティをサポートしていて、それを変更することができたかもしれません。そのような環境では、このようにすることができます。

Employee.__proto__ = Person; // Was non-standard in ES5

もし、何らかの理由でこれを function 構文の代わりに class を使用すると、ES2015+環境では、標準の Object.setPrototypeOf を使用します。

Object.setPrototypeOf(Employee, Person); // Standard ES2015+

しかし、ES2015+環境で古い構文を使用する強い動機が見当たりません(配管がどのように機能するかを理解するための実験以外には)。

(ES2015 はまた __proto__ アクセサー・プロパティ のラッパーです。 Object.setPrototypeOfObject.getPrototypeOf を追加することで、これらの非標準的な環境でのコードが標準になるようにしましたが、これはレガシーコードに対してのみ定義され、環境がこれを提供することを要求されないという意味で "規範的オプション" です)。


¹ 以下では Reflect.construct をサブクラスとして Error を使いたくなかった場合、(例えば) class の構文を使うことができます。

// Creating an Error subclass:
function MyError(...args) {
  return Reflect.construct(Error, args, this.constructor);
}
MyError.prototype = Object.create(Error.prototype);
MyError.prototype.constructor = MyError;
MyError.prototype.myMethod = function() {
  console.log(this.message);
};

// Example use:
function outer() {
  function inner() {
    const e = new MyError("foo");
    console.log("Callng e.myMethod():");
    e.myMethod();
    console.log(`e instanceof MyError? ${e instanceof MyError}`);
    console.log(`e instanceof Error? ${e instanceof Error}`);
    throw e;
  }
  inner();
}
outer();
.as-console-wrapper {
  max-height: 100% !important;
}