サーチ…
構文
- クラスFoo {}
- クラスFooはBar {}を拡張します
- クラスFoo {コンストラクタ(){}}
- クラスFoo {myMethod(){}}
- クラスFoo {get myProperty(){}}
- クラスFoo {set myProperty(newValue){}}
- クラスFoo {static myStaticMethod(){}}
- クラスFoo {static get myStaticProperty(){}}
- const Foo =クラスFoo {};
- const Foo =クラス{};
備考
class
サポートは、2015 es6標準の一部としてJavaScriptに追加されました。
Javascriptクラスは、JavaScriptの既存のプロトタイプベースの継承を超える構文上の砂糖です。この新しいシンタックスでは、オブジェクト指向の継承モデルをJavaScriptに導入することはなく、オブジェクトや継承を処理する簡単な方法です。 class
宣言は、基本的に、コンストラクタfunction
を手動で定義し、そのコンストラクタのプロトタイプにプロパティを追加するための省略表現です。重要な違いは、関数を直接( new
キーワードなしで)呼び出すことができますが、直接呼び出されたクラスは例外をスローする点です。
class someClass {
constructor () {}
someMethod () {}
}
console.log(typeof someClass);
console.log(someClass);
console.log(someClass === someClass.prototype.constructor);
console.log(someClass.prototype.someMethod);
// Output:
// function
// function someClass() { "use strict"; }
// true
// function () { "use strict"; }
以前のバージョンのJavaScriptを使用している場合は、ターゲットプラットフォームが理解できるバージョンにコードをコンパイルするために、 babelやgoogle-closure-compilerのようなトランスパイライザーが必要です。
クラスコンストラクタ
ほとんどのクラスの基本的な部分は、各インスタンスの初期状態を設定し、 new
呼び出すときに渡されたパラメータを処理するコンストラクタです。
これは実際に特殊なケースとして扱われますが、 constructor
という名前のメソッドを定義しているかのようにclass
ブロックで定義されています。
class MyClass {
constructor(option) {
console.log(`Creating instance using ${option} option.`);
this.option = option;
}
}
使用例:
const foo = new MyClass('speedy'); // logs: "Creating instance using speedy option"
static
キーワードを使ってクラスコンストラクタを静的にすることはできません。他のメソッドについては後述します。
静的メソッド
静的メソッドおよびプロパティは、クラス/コンストラクタ自体で定義され、インスタンスオブジェクトでは定義されません。これらは、 static
キーワードを使用してクラス定義で指定します。
class MyClass {
static myStaticMethod() {
return 'Hello';
}
static get myStaticProperty() {
return 'Goodbye';
}
}
console.log(MyClass.myStaticMethod()); // logs: "Hello"
console.log(MyClass.myStaticProperty); // logs: "Goodbye"
静的プロパティがオブジェクトインスタンスで定義されていないことがわかります。
const myClassInstance = new MyClass();
console.log(myClassInstance.myStaticProperty); // logs: undefined
ただし、それらはサブクラスで定義されています 。
class MySubClass extends MyClass {};
console.log(MySubClass.myStaticMethod()); // logs: "Hello"
console.log(MySubClass.myStaticProperty); // logs: "Goodbye"
ゲッターとセッター
GettersとSetterを使用すると、クラスのプロパティを読み書きするためのカスタム動作を定義できます。ユーザーには、典型的なプロパティと同じように見えます。ただし、内部的には、プロパティにアクセスするときの値(ゲッター)を決定し、プロパティが割り当てられたときに必要な変更(設定)を行うために、提供するカスタム関数を使用します。
class
定義では、getterは、引数なしのメソッドのget
キーワードで始まります。 setterは、1つの引数(新しい値が割り当てられます)を受け入れ、代わりにset
キーワードが使用される点を除いて同様です。
.name
プロパティのゲッターとセッターを提供するクラスの例を次に示します。割り当てられるたびに、新しい名前が内部の.names_
配列に記録されます。アクセスするたびに、最新の名前が返されます。
class MyClass {
constructor() {
this.names_ = [];
}
set name(value) {
this.names_.push(value);
}
get name() {
return this.names_[this.names_.length - 1];
}
}
const myClassInstance = new MyClass();
myClassInstance.name = 'Joe';
myClassInstance.name = 'Bob';
console.log(myClassInstance.name); // logs: "Bob"
console.log(myClassInstance.names_); // logs: ["Joe", "Bob"]
セッターを定義するだけであれば、プロパティにアクセスしようとすると常にundefined
が返されます。
const classInstance = new class {
set prop(value) {
console.log('setting', value);
}
};
classInstance.prop = 10; // logs: "setting", 10
console.log(classInstance.prop); // logs: undefined
ゲッターを定義するだけであれば、プロパティの割り当てを試みても効果はありません。
const classInstance = new class {
get prop() {
return 5;
}
};
classInstance.prop = 10;
console.log(classInstance.prop); // logs: 5
クラス継承
継承は、他のオブジェクト指向言語と同様に機能します。スーパークラスに定義されたメソッドは、拡張サブクラスでアクセス可能です。
サブクラスは、独自のコンストラクタを宣言した場合、それは経由して親のコンストラクタを呼び出す必要がありますsuper()
それがアクセスする前this
。
class SuperClass {
constructor() {
this.logger = console.log;
}
log() {
this.logger(`Hello ${this.name}`);
}
}
class SubClass extends SuperClass {
constructor() {
super();
this.name = 'subclass';
}
}
const subClass = new SubClass();
subClass.log(); // logs: "Hello subclass"
プライベートメンバー
JavaScriptは技術的にプライベートメンバーを言語機能としてサポートしていません。 Douglas Crockfordによって記述されたプライバシーはコンストラクタ関数のインスタンス化の呼び出しごとに生成されるクロージャ(保存された関数スコープ)を介して代わりにエミュレートされます。
Queue
例では、コンストラクタ関数を使用してローカルステートを保持し、特権メソッドによってアクセス可能にする方法を示します。
class Queue {
constructor () { // - does generate a closure with each instantiation.
const list = []; // - local state ("private member").
this.enqueue = function (type) { // - privileged public method
// accessing the local state
list.push(type); // "writing" alike.
return type;
};
this.dequeue = function () { // - privileged public method
// accessing the local state
return list.shift(); // "reading / writing" alike.
};
}
}
var q = new Queue; //
//
q.enqueue(9); // ... first in ...
q.enqueue(8); //
q.enqueue(7); //
//
console.log(q.dequeue()); // 9 ... first out.
console.log(q.dequeue()); // 8
console.log(q.dequeue()); // 7
console.log(q); // {}
console.log(Object.keys(q)); // ["enqueue","dequeue"]
Queue
型の各インスタンス化では、コンストラクタがクロージャを生成します。
このように、両方のQueue
種類の独自のメソッドのenqueue
とdequeue
(参照Object.keys(q)
)まだへのアクセス持っているlist
構築時に、保存されている、ことを囲みスコープに住み続けています。
特権を持つパブリックメソッドを使用してプライベートメンバーをエミュレートするこのパターンを利用すると、すべてのインスタンスで追加のメモリがすべてのプロパティメソッドで消費されることに注意する必要があります(共有/再利用できないコード)。そのようなクロージャ内に格納される状態の量/サイズについても同じことが当てはまります。
動的メソッド名
[]
オブジェクトのプロパティにアクセスする方法に似た名前を付けるときに、式を評価する機能もあります。これは動的なプロパティ名を持つのに便利ですが、しばしば記号と組み合わせて使用されます。
let METADATA = Symbol('metadata');
class Car {
constructor(make, model) {
this.make = make;
this.model = model;
}
// example using symbols
[METADATA]() {
return {
make: this.make,
model: this.model
};
}
// you can also use any javascript expression
// this one is just a string, and could also be defined with simply add()
["add"](a, b) {
return a + b;
}
// this one is dynamically evaluated
[1 + 2]() {
return "three";
}
}
let MazdaMPV = new Car("Mazda", "MPV");
MazdaMPV.add(4, 5); // 9
MazdaMPV[3](); // "three"
MazdaMPV[METADATA](); // { make: "Mazda", model: "MPV" }
メソッド
メソッドをクラス内で定義して、関数を実行し、オプションで結果を返すことができます。
彼らは呼び出し元から引数を受け取ることができます。
class Something {
constructor(data) {
this.data = data
}
doSomething(text) {
return {
data: this.data,
text
}
}
}
var s = new Something({})
s.doSomething("hi") // returns: { data: {}, text: "hi" }
クラスによるプライベートデータの管理
クラスを使用する際の最も一般的な障害の1つは、プライベートな状態を処理するための適切なアプローチを見つけることです。プライベートな状態を扱うための4つの共通の解決策があります:
シンボルの使用
シンボルは、ES2015で導入された新しいプリミティブ型で、 MDNで定義されています
シンボルは、オブジェクトプロパティの識別子として使用できる一意で不変なデータ型です。
シンボルをプロパティキーとして使用する場合は、列挙できません。
そのfor var in
やObject.keys
を使っfor var in
Object.keys
れることはありません。
したがって、シンボルを使用してプライベートデータを格納することができます。
const topSecret = Symbol('topSecret'); // our private key; will only be accessible on the scope of the module file
export class SecretAgent{
constructor(secret){
this[topSecret] = secret; // we have access to the symbol key (closure)
this.coverStory = 'just a simple gardner';
this.doMission = () => {
figureWhatToDo(topSecret[topSecret]); // we have access to topSecret
};
}
}
symbols
は一意であるため、プライベートプロパティにアクセスするには元のシンボルを参照する必要があります。
import {SecretAgent} from 'SecretAgent.js'
const agent = new SecretAgent('steal all the ice cream');
// ok lets try to get the secret out of him!
Object.keys(agent); // ['coverStory'] only cover story is public, our secret is kept.
agent[Symbol('topSecret')]; // undefined, as we said, symbols are always unique, so only the original symbol will help us to get the data.
しかし、100%プライベートではありません。そのエージェントを壊そう! Object.getOwnPropertySymbols
メソッドを使用してオブジェクトシンボルを取得できます。
const secretKeys = Object.getOwnPropertySymbols(agent);
agent[secretKeys[0]] // 'steal all the ice cream' , we got the secret.
弱マップの使用
WeakMap
はWeakMap
ために追加された新しいタイプのオブジェクトです。
MDNで定義されている
WeakMapオブジェクトは、キーが弱く参照されるキーと値のペアの集合です。キーはオブジェクトでなければならず、値は任意の値にすることができます。
WeakMap
1つの重要な機能は、 MDNで定義されているWeakMap
です。
WeakMapのキーは弱く保持されます。これが意味することは、キーへの他の強い参照がない場合、ガベージコレクタによって項目全体がWeakMapから削除されるということです。
アイデアは、クラス全体の静的マップとしてWeakMapを使用して、各インスタンスをキーとして保持し、プライベートデータをそのインスタンスキーの値として保持することです。
したがって、クラス内のみWeakMap
コレクションにアクセスできます。
WeakMap
をWeakMap
てエージェントに試してみましょう:
const topSecret = new WeakMap(); // will hold all private data of all instances.
export class SecretAgent{
constructor(secret){
topSecret.set(this,secret); // we use this, as the key, to set it on our instance private data
this.coverStory = 'just a simple gardner';
this.doMission = () => {
figureWhatToDo(topSecret.get(this)); // we have access to topSecret
};
}
}
const topSecret
はモジュールクロージャ内で定義されており、インスタンスプロパティにバインドしていないので、このアプローチは完全にプライベートであり、エージェントtopSecret
到達することはできません。
コンストラクタ内のすべてのメソッドを定義する
ここでのアイデアは、コンストラクタ内のすべてのメソッドとメンバーを定義し、クロージャを使用してprivateメンバーにアクセスするthis
です。
export class SecretAgent{
constructor(secret){
const topSecret = secret;
this.coverStory = 'just a simple gardner';
this.doMission = () => {
figureWhatToDo(topSecret); // we have access to topSecret
};
}
}
この例でも、データは100%プライベートであり、クラス外には到達できないため、エージェントは安全です。
命名規則の使用
非公開の不動産の先頭に_
が付くことになります。
このアプローチでは、データは実際にはプライベートではありません。
export class SecretAgent{
constructor(secret){
this._topSecret = secret; // it private by convention
this.coverStory = 'just a simple gardner';
this.doMission = () => {
figureWhatToDo(this_topSecret);
};
}
}
クラス名バインド
ClassDeclarationのNameは、異なるスコープで異なる方法でバインドされています。
- クラスが定義されているスコープ -
let
バインディング - クラス自体のスコープ -
class {}
-const
バインディングの{
および}
内
class Foo {
// Foo inside this block is a const binding
}
// Foo here is a let binding
例えば、
class A {
foo() {
A = null; // will throw at runtime as A inside the class is a `const` binding
}
}
A = null; // will NOT throw as A here is a `let` binding
これは、関数 -
function A() {
A = null; // works
}
A.prototype.foo = function foo() {
A = null; // works
}
A = null; // works