![見出し画像](https://assets.st-note.com/production/uploads/images/164972523/rectangle_large_type_2_9648c0ef0ff9ef59048d833ec5419913.jpeg?width=1200)
TypeScript 入門の記録(65)プロを目指す人のためのTypeScript入門(49)第5章TypeScriptのクラス(9)
こんにちは。サイボウズ株式会社 開発本部 People Experienceチーム コネクト支援チームの貴島(@jnkykn)です。北海道で淡路島バーガーが食べられるお店ができて、しかも「あわぢびーる」が飲めるという話を酒井(@sakay_y)さんにご報告したら、「あわぢびーるのヴァイツェンは無限に飲めるので、ぜひ!」とおすすめされたので、次回はヴァイツェンを飲みたいというか、全種類制覇したいと思っています(無限に飲めるのは危険と思いつつ)数日前に、TypeScriptの学習記録マガジンをフォローしてくださった方がいらっしゃって、「まずい、1年以上放置してる!!!」と思いました。心を入れ替えて、TypeScriptの学習を再開します。
this
なんと、前回のTypeScript 入門の記録は、2023年8月20日。1年以上前ですね。第5章クラスの続き、thisについて試しながら学習します。
関数の中のthis
関数の中のthisは、その関数の呼び出し方によって変わるそうです。なんだって!?
うろたえずに、順を追って確認します。
class User54 {
name: string;
#age: number;
constructor(name: string, age:number){
this.name = name;
this.#age = age;
}
// 成人ならtrueを返すメソッド
isAdult(): boolean {
return this.#age >= 18;
}
// ageを設定するメソッド
setAge(newAge: number) {
this.#age = newAge;
}
}
// 関数の中のthisは、呼び出し方で決まる
const uhyo541 = new User54("uhyo", 26);
const john541 = new User54("Jhon Smith", 15);
console.log(`uhyo541.isAdult === john541.isAdult = ${uhyo541.isAdult === john541.isAdult}`);
console.log(`uhyo541.isAdult() = ${uhyo541.isAdult()}`);
console.log(`jhon541.isAdult() = ${john541.isAdult()}`);
console.log(`uhyo541.isAdult() === john541.isAdult() = ${uhyo541.isAdult() === john541.isAdult()}`);
生成したインスタンスの関数は、クラスの関数を使用します。上記の例の場合、クラスの関数は、インスタンスにはコピーされず、uhyo541.isAdultとjhon.isAdultはUser54.isAdultを指すということです。エコですね。
![クラスのインスタンスの関数オブジェクトは、クラスの関数オブジェクトを指している](https://assets.st-note.com/img/1733658826-iA4oQrT6Z7pGMzUyBR309YXj.jpg?width=1200)
呼ばれた関数は、インスタンスのメンバーの値を使って結果を返します。上記の実行結果は、こうなります。
$ npx tsc
$ node dist/index_5-4.js
uhyo541.isAdult === john541.isAdult = true
uhyo541.isAdult() = true
jhon541.isAdult() = false
uhyo541.isAdult() === john541.isAdult() = false
メモリ上の関数オブジェクトの場所は同じだけれど、関数の実行時にインスタンスのメンバーを使うので、結果は想定どおり26歳のuhyo541さんは成人、15歳のjhon541さんは成人ではないという結果が得られます。
ここで関数オブジェクトを変数に代入して呼び出してみます。
const isAdult = uhyo541.isAdult;
console.log(`isAdult() = ${isAdult()}`);
実行してみると、関数内でメンバー変数が使えず、エラーが発生しました。
TypeError: Cannot read private member from an object whose class did not declare it
関数オブジェクトを代入した変数は、所属するインスタンスがundefinedなため、メンバー変数を参照できないのですね。(非strictモードだと、thisはグローバル変数になるので、実行できてしまうらしいです)
アロー関数内のthis
アロー関数は自身のthisを持たないので、thisを外側の関数から受け継ぎます。
アロー関数内のthisは外側の関数のthisと同じ
関数内にないアロー関数の場合、アロー関数内のthisはundefined
また、ややこしいですね。
確かめるために、関数を追加。
public filterOlder(users: readonly User54[]): User54[] {
return users.filter(u => u.#age > this.#age);
}
}
// 関数の中のthisは、呼び出し方で決まる
const uhyo541 = new User54("uhyo", 26);
const john541 = new User54("Jhon Smith", 15);
const bob542 = new User54("Bob", 40);
const older = uhyo541.filterOlder([john541, bob542]);
console.log(older);
結果を見ると、呼び出し元のuhyo541.#ageをthis.#ageとして、パラメータで渡した配列の#ageと比較した結果を返しています。
$ node dist/index_5-4.js
[ User54 { name: 'Bob' } ]
今度は、この関数を通常の関数式で書き直してみます。(thisがundefinedになる想定)
![アロー関数を単純な関数として書き直してみると、thisがanyなのでエラー](https://assets.st-note.com/img/1733655356-OixVZHQG42fsXAbS3kY5qlwT.png?width=1200)
パラメータで、thisの型を指定してみます。
public filterOlder(users: readonly User54[]): User54[] {
return users.filter(function(this: User54, u) {return u.#age > this.#age}) ;
}
実行してみると、プライベートメンバーが読めないというエラーが発生しました。渡したパラメータthisのメンバーが参照できないようです。
TypeError: Cannot read private member from an object whose class did not declare it
これは、users.filterからは、呼び出し元が見えないことが原因なので、一旦受け取ったパラメータのthisを変数に退避して、見えるようにすると解決できるようです。というわけで、やってみます。
public filterOlder(users: readonly User54[]): User54[] {
// this を一時退避する
const _this = this;
return users.filter(function(u) {return u.#age > _this.#age}) ;
}
実行してみると、期待通りの結果になりました。
$ node dist/index_5-4.js
[ User54 { name: 'Bob' } ]
これは、わかりにくいですね。アロー関数で記述すればスッキリ期待通りの結果が得られるので、関数内でthisを引き継ぎたい場合は、アロー関数にしようと思います。
thisを操作するメソッド
関数の呼び出し方のうち、オブジェクトが持つ、applyメソッドと、callメソッドを試してみます。applyメソッドは、オブジェクト内のメソッドを呼び出すメソッドです。func.apply(obj, args)という形式で呼び出し、func内のthisを、パラメータobjと置き換えます。
const uhyo541 = new User54("uhyo", 26);
const john541 = new User54("Jhon Smith", 15);
const bob542 = new User54("Bob", 40);
console.log(`uhyo541.isAdult() = ${uhyo541.isAdult()}`);
console.log(`uhyo541.isAdult.apply(john541, []) = ${uhyo541.isAdult.apply(john541, [])}`);
実行してみると、uhyo541.isAdult()はtrueですが、uhyo541.isAdult.apply (john541, []) の結果は、john.#ageが15なので、falseです。
$ node dist/index_5-4.js
uhyo541.isAdult() = true
uhyo541.isAdult.apply(john541, []) = false
applyと同様に、callも試します。callは、呼び出すときのパラメータのargsを配列ではなく、第2引数以降に列挙するところが違うだけで使い方は同じです。
console.log(`uhyo541.isAdult() = ${uhyo541.isAdult()}`);
console.log(`uhyo541.isAdult.call(john541) = ${uhyo541.isAdult.call(john541)}`);
実行時のthisがパラメータで渡されたオブジェクトと置き換えられるので、実行結果はapplyと同様です。
$ node dist/index_5-4.js
uhyo541.isAdult() = true
uhyo541.isAdult.call(john541) = false
thisを固定する、bindも試します。
const boundIsAdult = uhyo541.isAdult.bind(uhyo541);
console.log(`boundIsAdult() = ${boundIsAdult()}`);
console.log(`boundIsAdult(john541) = ${boundIsAdult.call(john541)}`);
実行してみると、callで#age15のjohn541を渡しても、uhyo541は26歳なのでtrueが返りました。
$ node dist/index_5-4.js
boundIsAdult() = true
boundIsAdult(john541) = true
まとめ
久しぶりにTypeScriptの学習を再開しました、普段雰囲気でコーディングしているので、ちゃんと理解した上で使いたいと改めて思いました。区切りが良いので、今週はここまでにします。
来週の今頃は、P2HACKS 2024発表会に出席、サイボウズ賞の受賞チームも決まって、函館で協賛報告noteを書いている予定です。
公立はこだて未来大学学内ハッカソンP2HACKS2024が、始まりました!
— Cybozu Inside Out (@cybozuinsideout) December 7, 2024
サイボウズは、今年もP2HACKSに協賛しています。
今年のテーマは「フラッシュ」
どんなプロダクトが生まれるか、楽しみです!皆さんのチームワークを、応援しています! #p2hacks #CybozuTech https://t.co/aD2lYD4Jyn pic.twitter.com/XPJiSl4cge