🌌
[JavaScript] クラス構文による独自データ型とパターンマッチ
[JavaScript] クラス構文による独自データ型とパターンマッチ
生jsで素朴にオブジェクト指向プログラミングをするまでの理解の手順
jsのクラス構文による独自データ型とパターンマッチできるまでがゴール
目次
- jsでの"クラス"
- jsでのfunctionオブジェクト
- プロトタイプベースのオブジェクト指向
- プロトタイプチェーン
- jsでのclass構文
- なぜObject()はできてclass Hoge {} のHoge()はできないのか
- プロトタイプメソッドとインスタンスメソッドの違い
- インスタンスメソッド > プロトタイプメソッド
- プロトタイプチェーンのフォールバック
- インスタンスが所属するクラスとそのクラス名の取得
- jsのクラス構文による独自データ型とパターンマッチ
jsでの"クラス"
jsにおいて全てクラスめいたものはfunctionオブジェクトとして定義されている
typeof Object // -> 'function'
typeof class{} // -> 'function'
jsでのfunctionオブジェクト
データと機能の集合≒オブジェクト
第一級として振る舞うデータと処理のまとまり
プロトタイプベースのオブジェクト指向
__proto__がネストしていくことで継承を実現している(プロトタイプチェーン)
プロトタイプチェーン
- インスタンス作成時にインスタンスの__proto__プロパティにプロトタイプオブジェクトの参照を保存
- インスタンスからメソッドを参照するとき__proto__内部プロパティまで辿る
jsでのclass構文
objectとprototypeを簡単に書くための糖衣構文
実体はfunctionオブジェクト
typeof class {} // -> 'function'
class宣言とclass式、名前付きclass式の3種類
特に明示的にプロトタイプチェーンを設定しなくて済む
functionベース
function Hoge(){}
function Fuga(){}
Fuga.prototype.__proto__ = Hoge.prototype;
classベース
class Hoge {}
class Fuga extends Hoge {}
Fuga.prototype.__proto__ === Hoge.prototype
// -> true
なぜObject()はできてclass Hoge {} のHoge()はできないのか
そもそもビルトインオブジェクトのObjectはfunctionベースで定義
class構文ではfunctionとして呼び出されないようにHoge()でのコンストラクタ呼び出しを禁止している(ランタイムエラーになる)
class Hoge() {}
Hoge()
// Uncaught TypeError: Class constructor Hoge cannot be invoked without 'new' at <anonymous>:1:1
ビルトインオブジェクトの大半はfunctionベースでクラスめいたものとして定義されてる
が、比較的新しいMapオブジェクトとかはMap()はできない
プロトタイプメソッドとインスタンスメソッドの違い
-
定義の仕方
- インスタンスメソッド
- thisに気をつけてconstructorにアロー関数で定義
- プロトタイプメソッド
- class宣言/式直下にメソッドとして定義 -> prototypeにメソッドが生える
- インスタンスメソッド
-
継承のされ方
- インスタンスメソッド
- constructorのsuperでインスタンスに動的にinject
- プロトタイプメソッド
- 親のprototypeにある定義を辿る
- インスタンスメソッド
インスタンスメソッド > プロトタイプメソッド
同時に定義できるがオーバーライドではない
そのため、インスタンスメソッドをdeleteするとプロトタイプメソッドが参照されるようになる
class Hoge {
constructor(){
this.greet = () => console.log('hello') // インスタンスメソッド
}
greet() { // プロトタイプメソッド
console.log('こんにちは')
}
}
const hoge = new Hoge()
hoge.greet() // -> 'hello'
delete hoge.greet
hoge.greet() // -> 'こんにちは'
プロトタイプチェーンのフォールバック
dynamic languages - How does JavaScript .prototype work? - Stack Overflow
インスタンスが所属するクラスとそのクラス名の取得
class Class {}
const class = new Class()
class.constructor === Class // -> true
class.constructor.name // -> "Class"
jsのクラス構文による独自データ型とパターンマッチ
jsで素朴なオブジェクト指向プログラミング
class Animal {
constructor(num) {
if(num === undefined) throw 'animal type number must exist';
this.num = Number(num)
}
get bark(){ throw 'bark must be implemented' }
get fight(){ throw 'fight must be implemented' }
get act(){
this.bark
this.fight
}
static doSomething(animal) { // like pattern matching
if(animal.constructor === Dog) return console.log('do some dog execution');
if(animal.constructor === Cat) return console.log('do some cat execution');
//if(animal.constructor.name === Dog.name) return console.log('do some dog execution');
//if(animal.constructor.name === Cat.name) return console.log('do some cat execution');
throw 'not match any animal type'
}
static parse(str) {
if(str === "Dog" || str === 0 || str === "0") return new Dog();
if(str === "Cat" || str === 1 || str === "1") return new Cat();
throw 'cannot parse to any animal type'
}
}
class Dog extends Animal {
constructor(){
super(0)
}
get bark(){ return console.log("bowwow") }
get fight(){ return console.log('bite') }
}
class Cat extends Animal {
constructor(){
super(1)
}
get bark(){ return console.log("meow") }
get fight(){ return console.log('run away') }
}
const dog = new Dog()
const cat = new Cat()
const fish = new (class Fish{})()
dog.num // -> 0
cat.num // -> 1
//fish.num // -> undefined
Animal.doSomething(dog) // -> do some dog execution
Animal.doSomething(cat) // -> do some cat execution
// Animal.doSomething(fish) // -> Uncaught animal type number not found
Animal.parse("Dog") // -> Dog {num: 0}
Animal.parse(1) // -> Cat {num: 1}
// Animal.parse(2) // -> Uncaught cannot parse to any animal type
dog.act
// -> bowwow
// -> bite
cat.act
// -> meow
// -> run away
上記から特にパターンマッチ部分を抽出
static doSomething(animal) { // like pattern matching
if(animal.constructor === Dog) return console.log('do some dog execution');
if(animal.constructor === Cat) return console.log('do some cat execution');
//if(animal.constructor.name === Dog.name) return console.log('do some dog execution');
//if(animal.constructor.name === Cat.name) return console.log('do some cat execution');
throw 'not match any animal type'
}
クラスの一致/不一致でパターンマッチめいたものを実装する
コンパイラの恩恵は受けれないため実装漏れはランタイムエラーを投げる
Discussion