2022年1月14日 JavaScript (JS Primer) 静的メソッド
インスタンスメソッドは、クラスをインスタンス化して利用します。 一方、クラスをインスタンス化せずに利用できる静的メソッド(クラスメソッド)もあります。
静的メソッドの定義方法はメソッド名の前に、staticをつけるだけです。
- 静的メソッドはクラスメソッドです。
- 定義方法はメソッド名の前に
static
をつけるだけです。
class クラス { static メソッド() { // 静的メソッドの処理 } } // 静的メソッドの呼び出し クラス.メソッド();
次のコードでは、配列をラップするArrayWrapperというクラスを定義しています。 ArrayWrapperはコンストラクタの引数として配列を受け取って初期化しています。 このクラスに配列ではなく要素そのものを引数に受け取ってインスタンス化できるArrayWrapper.ofという静的メソッドを定義します。
下のコードで定義しているarrayWrapperBはクラスのインスタンス化にnew演算子を用いていません。
クラスに定義したofという静的メソッドのコンストラクタの引数をrest parametersとして要素を受けつけてインスタンス化しています。
class ArrayWrapper { constructor(array = []) { this.array = array; } // rest parametersとして要素を受けつける static of(...items) { return new ArrayWrapper(items); } get length() { return this.array.length; } } // 配列を引数として渡している const arrayWrapperA = new ArrayWrapper([1, 2, 3]); // 要素を引数として渡している const arrayWrapperB = ArrayWrapper.of(1, 2, 3); console.log(arrayWrapperA.length); // => 3 console.log(arrayWrapperB.length); // => 3
復習
- Rest parametersは、仮引数名の前に...をつけた仮引数のことで、残余引数とも呼ばれます。 Rest parametersには、関数に渡された値が配列として代入されます。
クラスの静的メソッドにおけるthisは、そのクラス自身を参照します。 そのため、先ほどのコードはnew ArrayWrapperの代わりにnew thisと書くこともできます。
重要なポイント
- クラスの静的メソッドにおける
this
は、そのクラス自身参照します。
参考
2022年1月13日 JavaScript (JS Primer) Array#lengthをアクセッサプロパティで再現する
復習
- getterは"インスタンス.プロパティ名"とする事でプロパティを参照した時に実行されます。getterは値を返すものです。
- setterは"インスタンス.プロパティ名 = 値"とする事で値を代入した時に、値が仮引数となって実行されます。
Array#lengthをアクセッサプロパティで再現する
getterやsetterを利用しないと実現が難しいものとしてArray#lengthプロパティがあります。
以下はlengthプロパティの仕組みです。
const array = [1, 2, 3, 4, 5]; // 要素数を減らすと、インデックス以降の要素が削除される array.length = 2; console.log(array.join(", ")); // => "1, 2" // 要素数だけを増やしても、配列の中身は空要素が増えるだけ array.length = 5; console.log(array.join(", ")); // => "1, 2, , , "
このlengthプロパティの挙動を再現するArrayLikeクラスを実装してみます。
/** * 配列のようなlengthを持つクラス */ class ArrayLike { constructor(items = []) { this._items = items; } get items() { return this._items; } get length() { return this._items.length; } set length(newLength) { const currentItemLength = this.items.length; // 現在要素数より小さな`newLength`が指定された場合、指定した要素数となるように末尾を削除する if (newLength < currentItemLength) { this._items = this.items.slice(0, newLength); } else if (newLength > currentItemLength) { // 現在要素数より大きな`newLength`が指定された場合、指定した要素数となるように末尾に空要素を追加する this._items = this.items.concat(new Array(newLength - currentItemLength)); } } } const arrayLike = new ArrayLike([1, 2, 3, 4, 5]); // 要素数を減らすとインデックス以降の要素が削除される arrayLike.length = 2; console.log(arrayLike.items.join(", ")); // => "1, 2" // 要素数を増やすと末尾に空要素が追加される arrayLike.length = 5; console.log(arrayLike.items.join(", ")); // => "1, 2, , , "
Array.prototype.slice()
第一引数に開始位置、第二引数に終了位置を指定し、その範囲を取り出した新しい配列を返します。
・第二引数は省略でき、省略した場合は文字列の末尾が終了位置となります。
・終了位置のインデックスの要素は取り出してくれないというのが意外な部分です。
const array = ["A", "B", "C", "D", "E"]; // インデックス1から4の手前の範囲を取り出します。 console.log(array.slice(1, 4)); // => ["B", "C", "D"]
Array.prototype.join()
join() メソッドは、配列 (または配列風オブジェクト) の全要素を順に連結した文字列を新たに作成して返します。区切り文字はカンマ、または指定された文字列です。配列にアイテムが一つしかない場合は、区切り文字を使用せずにアイテムが返されます。
joinメソッドは仮引数に何も渡さないと,
で区切ってくれるので今回の出力結果と同じですが、,
とコンマの後に半角スペースを持たせるためにあえて明示されていました。配列を実際にコードで書くときには半角スペースを入れるので、join
を使用した場合も今回のコード例のように入れた方が丁寧だと感じました。
何を返す?・・・文字列を返します。
どんな文字列?・・・配列の要素をコンマ付きでつなげた文字列。
引数は何を設定?・・・コンマの部分に差し込むもの
デフォルト引数
JavaScript では、関数の引数は、指定しなければ undefined になります。
- constructor(items = [])とすることで例外やundefinedが出力されるケースで代わりに空配列を出力するという宣言をしています。
インスタンス化する際に引数を持たせなかった場合の出力結果です。
constructor(items = []) { this._items = items; }
・ デフォルト引数を使わない場合(constructor(items)とした場合)
TypeError: Cannot read properties of undefined (reading 'length')
・ デフォルト引数を使った場合(constructor(items = [])とした場合)
const arrayLike = new ArrayLike(); console.log(arrayLike); 出力結果 { _items: [] }
参考
JS primer Array#lengthをアクセッサプロパティで再現する
Array.prototype.join() - JavaScript | MDN
2022年1月10日 JavaScript (JS Primer) クラスのアクセッサプロパティの定義
クラスのアクセッサプロパティの定義
クラスに対してメソッドを定義できますが、メソッドはメソッド名()のように呼び出す必要があります。
クラスでは、プロパティの参照(getter)、プロパティへの代入(setter)時に呼び出される特殊なメソッドを定義できます。
定義方法はメソッド名(プロパティ名)の前にget
またはset
をつけるだけです。
getter(
get
)には仮引数はありませんが、必ず値を返す必要があります。
setter(set
)の仮引数にはプロパティへ代入する値が入りますが、値を返す必要はありません。
- getterはプロパティの値を返します。参照時に呼び出されます。
- setterは値を代入します。代入時に呼び出されます。
class クラス { // getter get プロパティ名() { return 値; } // setter set プロパティ名(仮引数) { // setterの処理 } } const インスタンス = new クラス(); インスタンス.プロパティ名; // getterが呼び出される インスタンス.プロパティ名 = 値; // setterが呼び出される
次のコードでは、NumberWrapperクラスのvalueプロパティをアクセッサプロパティとして定義しています。 valueプロパティへアクセスした際にそれぞれ定義したgetterとsetterが呼ばれているのがわかります。
このアクセッサプロパティで実際に読み書きされているのは、NumberWrapperインスタンスの_valueプロパティとなります。
class NumberWrapper { constructor(value) { this._value = value; } // `_value`プロパティの値を返すgetter get value() { console.log("getter"); return this._value; } // `_value`プロパティに値を代入するsetter set value(newValue) { console.log("setter"); this._value = newValue; } } const numberWrapper = new NumberWrapper(1); // "getter"とコンソールに表示される console.log(numberWrapper.value); // => 1 // "setter"とコンソールに表示される numberWrapper.value = 42; // "getter"とコンソールに表示される console.log(numberWrapper.value); // => 42
出力結果
getter
1
setter
getter
42
以下の流れで出力されています。
- new演算子に引数を渡します。
- console.log(numberWrapper.value);とするとプロパティが参照されたため、getterが呼び出され、console.log();の出力とvalueである”1”が返されます。
- numberWrapper.value = 42;とすることでsetterが呼び出されます。
- console.log();の出力とプロパティを参照して代入が行われます。
- 最後のconsole.log(numberWrapper);でgetterが再度呼び出され、代入された42が出力されます。
メモ
- セッターはクラス内の変数に値を設定する関数です。
- ゲッターはクラス内の変数から値を取得して呼び出し元に返す関数です。
- クラスの中に用意する変数は、専門用語で「メンバ変数」と呼ばれます。
- クラスの中に用意する関数は、専門用語で「メソッド」と呼ばれます。
参考
2022年1月8日 JavaScript (JS Primer) クラスのプロトタイプメソッドの定義
クラスのインスタンスに対してメソッドを定義する
前回はプロトタイプメソッドにメソッドを定義したケースを学習しましたが、 今回はインスタンスオブジェクトにメソッドを定義した場合についてまとめていきます。
下のコード例ではCounterクラスのコンストラクタ関数で、インスタンスオブジェクトにincrementメソッドを定義しています。
class Counter { constructor() { this.count = 0; this.increment = () => { this.count++; }; } } const counterA = new Counter(); const counterB = new Counter(); // `counterA.increment()`のベースオブジェクトは`counterA`インスタンス counterA.increment(); // 各インスタンスの持つプロパティ(状態)は異なる console.log(counterA.count); // => 1 console.log(counterB.count); // => 0
前回ではclass内でconstructor()とは別にメソッドを定義していましたが、今回はconstructor()内でthisに対してメソッドを定義しています。
呼び出し方は前回と同じです。
counterA.increment();
今回の方法で定義したincrementメソッドはインスタンスから呼び出せるため、インスタンスメソッドと言えます。
しかし、インスタンスオブジェクトに定義したincrement
メソッドはプロトタイプメソッドではありません。
異なる点を2点、挙げます。
①各インスタンスオブジェクトのメソッドの参照先は異なる
インスタンス間で共有できるメソッドをプロトタイプメソッドと呼びますが、インスタンスオブジェクトにメソッドを定義すると、共有できるメソッドではなくなってしまいます。
インスタンス間の共有 | インスタンスから呼び出せる | |
---|---|---|
プロトタイプメソッド(インスタンスメソッド) | ⭕️ | ⭕️ |
インスタンスメソッド(コンスタラクタ関数内のthisにメソッドを定義した場合) | ❌ | ⭕️ |
プロトタイプメソッドとして定義していた場合trueになっていた例ですが、今回の例では下のようにfalseになります。
console.log(counterA.increment === counterB.increment); // => false
②Arrow Functionが利用できる(thisにメソッドを定義する場合)
Arrow Functionにはthisが静的に決まるという性質があり、Arrow Functionで定義したメソッドはどのような呼び出し方をしても、常にインスタンスを参照します。 下の例を見るとメソッドにおけるthisの参照先がインスタンスに固定されているためmethodを呼び出すとinstanceを参照出来ることがわかります。
"use strict"; class ArrowClass { constructor() { // コンストラクタでの`this`は常にインスタンス this.method = () => { // Arrow Functionにおける`this`は静的に決まる // そのため`this`は常にインスタンスを参照する return this; }; } } const instance = new ArrowClass(); const method = instance.method; // 呼び出し方法(ベースオブジェクト)に依存しないため、`this`がインスタンスを参照する console.log(method()); // => instance
一方、プロトタイプメソッドでは、通常のthisの挙動と同様にベースオブジェクトを参照するので、呼び出し時にメソッドでなければundefined
になってしまいます。
"use strict"; class PrototypeClass { method() { // `this`はベースオブジェクトを参照する return this; }; } const instance = new PrototypeClass(); const method = instance.method; // ベースオブジェクトはundefined console.log(method()); // => undefined
復習
- thisは呼び出し時に定義されるため、基本的に呼び出し時にメソッドでなければundefinedになってしまいます。
メモ
参考
2022年1月6日 JavaScript (JS Primer) クラスのプロトタイプメソッドの定義
- クラスの動作はメソッドによって定義できます。
- class構文ではクラスに対して自由にメソッドを定義できます。
- このクラスに定義したメソッドは作成したインスタンスが持つ動作となります。
- メソッドでクラスのインスタンスを参照するには、thisを使います。 new演算子で生成したインスタンスオブジェクトがベースオブジェクト、つまり『this』となります。
クラス構文の中で定義したthis
はインスタンス変数を指す。 とシンプルに覚えてもいいかも知れません。
class クラス { メソッド() { // ここでの`this`はベースオブジェクトを参照 } } const インスタンス = new クラス(); インスタンス.メソッド(); // メソッドの呼び出し
クラスのプロトタイプメソッド定義では、オブジェクトにおけるメソッドとは異なり
key : value
のように:
区切りでメソッドを定義できないことに注意してください。 つまり、次のような書き方は構文エラー(SyntaxError
)となります。
- メソッドの短縮記法以外はSyntaxErrorとなるそうです。
- クラス構文で定義するプロトタイプメソッドはメソッドの短縮記法で書く必要がある。 と覚えておくと良いかも知れません。
class クラス { // SyntaxError メソッド: () => {} // SyntaxError メソッド: function(){} }
このようにクラスに対して定義したメソッドは、クラスの各インスタンスから共有されるメソッドとなります。 このインスタンス間で共有されるメソッドのことをプロトタイプメソッドと呼びます。 また、プロトタイプメソッドはインスタンスから呼び出せるメソッドであるためインスタンスメソッドとも呼ばれます。
以下のコード例ではCounterクラスにincrementメソッドを定義しています。 Counterクラスのインスタンスはそれぞれ別々の状態(countプロパティ)を持ちます。
class Counter { constructor() { this.count = 0; } increment() { this.count++; } } const counterA = new Counter(); const counterB = new Counter(); // `counterA.increment()`のベースオブジェクトは`counterA`インスタンス counterA.increment(); // 各インスタンスの持つプロパティ(状態)は異なる console.log(counterA.count); // => 1 console.log(counterB.count); // => 0
++
がインクリメント演算子と呼ぶので、ここではプロトタイプメソッド名としてincrement
と命名されています。このようにメソッド名からどんな動作をするメソッドかを理解できるものにする事はとても重要です。
インスタンス間でのメソッドの共有
プロトタイプメソッドは各インスタンス間で共有されます。 既存のプロトタイプメソッドを例に検証してみました。 以下が検証したコード例です。
console.log(Object.toString); console.log(Array.toString); console.log(Object.toString === Array.toString); 出力結果 function toString() { [native code] } function toString() { [native code] } true
生成されるインスタンスのプロトタイプメソッドが一致していることが確認出来ました。
参考
2022年1月4日 JavaScript (JS Primer) クラスのインスタンス化
前提知識
- new演算子
開発者はユーザー定義のオブジェクト型やコンストラクタ関数を持つ組み込みオブジェクト型のインスタンスを作成することができます。 (MDNリファレンス)
クラスのインスタンス化の流れ
class MyClass { } // `MyClass`をインスタンス化する const myClass = new MyClass(); // 毎回新しいインスタンス(オブジェクト)を作成する const myClassAnother = new MyClass(); // それぞれのインスタンスは異なるオブジェクト console.log(myClass === myClassAnother); // => false
クラスのインスタンスかどうかはinstanceof
演算子で判定できます。
console.log(myClass instanceof MyClass); // => true console.log(myClassAnother instanceof MyClass); // => true
クラスではインスタンスの初期化処理をコンストラクタ関数で行います。 コンストラクタ関数はnew演算子でインスタンス化する際に自動的に呼び出されます。 コンストラクタ関数内でのthisはこれから新しく作るインスタンスオブジェクトとなります。
下のコード例では x座標とy座標の値を持つPointというクラスを定義し、 コンストラクタ関数(constructor)の中でインスタンスオブジェクト(this)のxとyプロパティに値を代入して初期化しています。
class Point { // コンストラクタ関数の仮引数として`x`と`y`を定義 constructor(x, y) { // コンストラクタ関数における`this`はインスタンスを示すオブジェクト // インスタンスの`x`と`y`プロパティにそれぞれ値を設定する this.x = x; this.y = y; } }
実際のコンストラクタ関数の定義
class Point { // 2. コンストラクタ関数の仮引数として`x`には`3`、`y`には`4`が渡る constructor(x, y) { // 3. インスタンス(`this`)の`x`と`y`プロパティにそれぞれ値を設定する this.x = x; this.y = y; // コンストラクタではreturn文は書かない } } // 1. コンストラクタを`new`演算子で引数とともに呼び出す const point = new Point(3, 4); // 4. `Point`のインスタンスである`point`の`x`と`y`プロパティには初期化された値が入る console.log(point.x); // => 3 console.log(point.y); // => 4
ポイント
this
はインスタンス変数を指します。(この挙動は今まで学んできたthis
と違っているためこちらの素晴らしい記事を参考に勉強し納得しました)- コンストラクタ関数は
new演算子
で引数と共に呼び出したときに実行されます。
関数とクラスの役割の違い
クラスは”インスタンスを初期化する場所”という関数とは違う役割のため、通常の関数として呼び出すことは出来ません
class MyClass { constructor() { } } // クラスのコンストラクタ関数として呼び出すことはできない MyClass(); // => TypeError: class constructors must be invoked with |new|
エラー文を翻訳すると以下のように書いてありました。
- TypeError: class constructors must be invoked with |new|
(クラスコンストラクターは| new |で呼び出す必要があります)
インスタンスの返り値
コンストラクタは初期化処理を書く場所であるため、return
文で値を返すべきではありません。
以下のコード例は非推奨の例になります。
Classにおいてnew演算子の評価結果はクラスのインスタンスであることが妥当ですが、以下の例ではreturn文でオブジェクトを返しています。
instanceofを実行してもpointはPointクラスのインスタンスとして評価されていないことがわかります。
// 非推奨の例: コンストラクタで値を返すべきではない class Point { constructor(x, y) { // `this`の代わりにただのオブジェクトを返せる return { x, y }; } } // `new`演算子の結果はコンストラクタ関数が返したただのオブジェクト const point = new Point(3, 4); console.log(point); // => { x: 3, y: 4 } // Pointクラスのインスタンスではない console.log(point instanceof Point); // => false
メモ
- クラスはnew演算子でインスタンス化して使うものです。
- class構文で定義したクラスは関数として呼び出すことができません。
- 関数として呼び出すと「クラスはnew演算子で呼び出す必要があります。」というエラーが出力されます。
クラス名は大文字ではじめる
クラス名は大文字ではじめる慣習があります。Railsのように必ず大文字にしないとエラーになるというわけではありませんが、変数名が被ったりしないようにできる事と、クラス名として明示的になるので大文字から書くことが推奨されています。
class Thing {} const thing = new Thing();
参考
JS primer クラスのインスタンス化
Javascriptでオブジェクト指向するときに覚えておくべきこと
JavaScriptのthisの覚え方
🎊祝!本日で投稿数が100記事になりました!🎊
2022年1月3日 JavaScript (JS Primer) クラス
クラス
クラスの前提・・・動作や状態を定義した構造
class構文
- ES2015でクラスを表現するための
class
構文が導入されました class
構文で定義したクラスは関数オブジェクトの一種です。- class構文はクラスを作るための関数定義や継承をパターン化した書き方です。
関数の定義方法として関数宣言文と関数式があるように、クラスにもクラス宣言文とクラス式があります。 このように関数とクラスは似ている部分が多いです。
クラスの定義
クラスの定義方法にはクラス宣言文とクラス式があります。
クラス宣言文ではclass
キーワードを使い、class クラス名{ }
のようにクラスの構造を定義できます。
クラスは必ずコンストラクタを持ち、constructor
という名前のメソッドとして定義します。
そのクラスからインスタンスを作成する際にインスタンスに関する状態の初期化を行うメソッドです。
constructor
メソッドに定義した処理は、クラスをインスタンス化したときに自動的に呼び出されます。
クラス宣言文
class MyClass { constructor() { } }
コンストラクタの処理が不要な場合は省略出来ます。
クラス式
関数式のように値として定義する方法です。
const MyClass = class MyClass { constructor() {} }; const AnonymousClass = class { constructor() {} };
匿名関数のようにクラス名を省略することも出来ます。
復習
callメソッド
- 返値はthis の値と引数を指定して関数を呼び出した結果です。
- call() はあるオブジェクトに所属する関数やメソッドを、別なオブジェクトに割り当てて呼び出すことができます。
- call() は引数にthisの値を指定することができます。
- call()にプリミティブ型を渡すとラッパーオブジェクトが渡されたものとして処理されます。
call() は関数やメソッドに this の新しい値を提供します。
call() によって、いったんメソッドを書いてから、新しいオブジェクトへメソッドを書き直さずに他のオブジェクトへと継承することができます。
toString() を使用したオブジェクトクラスの判別にも使用することができます。
- toString() メソッドは、オブジェクトを表す文字列を返します。
const toString = Object.prototype.toString; toString.call(new Date); // [object Date] toString.call(new String); // [object String] toString.call(Math); // [object Math]
すべてのオブジェクトで Object.prototype.toString() を使うためには、 Function.prototype.call() または Function.prototype.apply() を、第1引数 (thisArg) に調べたいオブジェクトを渡して呼び出す必要があります。
callメソッドの用途
this
の値を指定したいとき- オブジェクトがどのオブジェクトに所属するか知りたいとき(プリミティブ型でもラッパーオブジェクトとして渡されるのでオブジェクトの型がわかります)