JavaScript でクラスをつくるとき
- カテゴリ:
- JavaScript
- コメント数:
- Comments: 0
◆ mixin 関数作ってそれからクラス作るようにすると多重継承みたいなことしやすい
◆ ライブラリで提供されるものはしかたない
◆ ライブラリで提供されるものはしかたない
クラス嫌いな私ですが 最近は CustomElements のためクラス書くことが多いです
慣れてくるとカンマの書き忘れがない分
でも良いかとも思えてきます
特にモジュールだとこれまで
みたいに 1 つしかつくらないだろうしとモジュール内のトップレベルにデータを保存しておく変数を作って関数をエクスポートしてましたが
みたいにクラスとしてエクスポートしてしまうとか
クラスとして定義してそのインタンスひとつをエクスポートするとかのほうが わかりやすいのかなとか思い始めてたりもします
元々のが モジュール内とはいえグローバル変数を書き換えるのに近いですし 共通して扱うデータはプロパティにして それを扱うものは同じクラスのメソッド それらを使わない独立した関数はクラス外に作って別にエクスポートするか static なメソッドにするほうが読みやすくなる気もします
新しく作るクラスでは すでにあるクラス 2 つの機能をつかいたいのですが 多重継承はできません
プロトタイプを直接いじるにしても クラス定義をすると prototype オブジェクトが書き換え不可なプロパティとしてつくられたり super 関数を呼び出すことが必須だったりいろいろ制限が付きます
この辺を無理やりやるというのは複雑で分かりづらいことになるのであんまり良い方法とは言えません
mixin という方法もありますが すでに存在するクラス 2 つをマージするようなことはできません
そこでクラスを普通に定義するのではなく mixin 関数をつくってクラスもそれで作るという作り方にします
B というクラスをインポートして それを継承したクラス A を作ってエクスポートする場合 普通に作ると
となるかと思います
これをこうします
クラス A の定義を関数内で行って その関数では引数として渡された関数を継承したクラスを返します
この関数の引数にクラス B を渡せば クラス B を継承したクラス A が返ってきます
このクラスと一緒に mixinA 関数もエクスポートすれば A の機能を含んだクラスを簡単に作れます
クラス C を継承したクラス D とクラス E があって 新しく作るクラス F は D と E の機能の両方を使いたいというときはこうできます
その場限りのクラス以外は全部 mixin 関数からつくるようにしても困ることはないと思うのでこうしておくのがいいかもですね
仕方ないので無理やりやるとこうなりました
f 関数の中身は
こうしたかったのですが クラス構文だと new で呼び出さないといけない制限があって自由に this を設定できないので それぞれを new した結果をプロトタイプチェーンでつなげてます
という感じで使えます
ただし 継承対象のクラスが他のクラスを継承してるとうまくいかないことがあります
コンストラクタはそれぞれで呼び出されて継承先も実行されます
メソッドは指定したものしか設定されないので親クラスのものは使えません
extend に親クラスも全部含めればメソッドは使えますが コンストラクタ呼び出しで親クラスのものは自分自身と子クラスのときの 2 度呼び出されるようになります
それと HTMLElement を継承してる場合はエラーになって動きません
こういう複雑なことしてるとクラス構文は自由度が足りなくて扱いづらいなと思いますね
慣れてくるとカンマの書き忘れがない分
const obj = new class {
foo(){}
bar(){}
}
でも良いかとも思えてきます
特にモジュールだとこれまで
const values1 = {}
const values2 = {}
function f1(){}
function f2(){}
export {f1, f2}
みたいに 1 つしかつくらないだろうしとモジュール内のトップレベルにデータを保存しておく変数を作って関数をエクスポートしてましたが
export default class {
constructor(){
this.values1 = {}
this.values2 = {}
}
f1(){}
f2(){}
}
みたいにクラスとしてエクスポートしてしまうとか
class c {
constructor(){
this.values1 = {}
this.values2 = {}
}
f1(){}
f2(){}
}
export default new c()
クラスとして定義してそのインタンスひとつをエクスポートするとかのほうが わかりやすいのかなとか思い始めてたりもします
元々のが モジュール内とはいえグローバル変数を書き換えるのに近いですし 共通して扱うデータはプロパティにして それを扱うものは同じクラスのメソッド それらを使わない独立した関数はクラス外に作って別にエクスポートするか static なメソッドにするほうが読みやすくなる気もします
mixin 対応させる
今回の本題はこれです新しく作るクラスでは すでにあるクラス 2 つの機能をつかいたいのですが 多重継承はできません
プロトタイプを直接いじるにしても クラス定義をすると prototype オブジェクトが書き換え不可なプロパティとしてつくられたり super 関数を呼び出すことが必須だったりいろいろ制限が付きます
この辺を無理やりやるというのは複雑で分かりづらいことになるのであんまり良い方法とは言えません
mixin という方法もありますが すでに存在するクラス 2 つをマージするようなことはできません
そこでクラスを普通に定義するのではなく mixin 関数をつくってクラスもそれで作るという作り方にします
B というクラスをインポートして それを継承したクラス A を作ってエクスポートする場合 普通に作ると
import B from "./b.js"
class A extends B {
method(){}
}
export default A
となるかと思います
これをこうします
import B from "./b.js"
const mixinA = cls => class extends cls {
method(){}
}
export default mixinA(B)
export { mixinA }
クラス A の定義を関数内で行って その関数では引数として渡された関数を継承したクラスを返します
この関数の引数にクラス B を渡せば クラス B を継承したクラス A が返ってきます
このクラスと一緒に mixinA 関数もエクスポートすれば A の機能を含んだクラスを簡単に作れます
クラス C を継承したクラス D とクラス E があって 新しく作るクラス F は D と E の機能の両方を使いたいというときはこうできます
// c.js
class C {
c(){return "c"}
}
export default C
// d.js
import C from "./c.js"
const mixinD = cls => class extends cls {
d(){return "d"}
}
export default mixinD(C)
export { mixinD }
// e.js
import C from "./c.js"
const mixinE = cls => class extends cls {
e(){return "e"}
}
export default mixinE(C)
export { mixinE }
// f.js
import C from "./c.js"
import D from "./d.js"
import E from "./e.js"
const mixinF = cls => class extends cls {
f(){return "f"}
}
export default mixinF(mixinE(mixinD(C)))
export { mixinF }
// g.js
import F from "./f.js"
const f = new F()
console.log(f.c())
console.log(f.d())
console.log(f.e())
console.log(f.f())
c
d
e
f
その場限りのクラス以外は全部 mixin 関数からつくるようにしても困ることはないと思うのでこうしておくのがいいかもですね
ライブラリだと
自分で作る関数だとこうしておけるのですが ライブラリに入ってるクラスはそうも行きません仕方ないので無理やりやるとこうなりました
function extend(...classes){
const f = function(...args){
let obj = f.prototype
for(const c of classes){
obj = Object.setPrototypeOf(new c(...args), obj)
}
return obj
}
for(const c of classes){
const descs = Object.getOwnPropertyDescriptors(c.prototype)
Object.defineProperties(f.prototype, descs)
}
return f
}
f 関数の中身は
const f = function(){
for(const c of classes){
c.apply(this, arguments)
}
}
こうしたかったのですが クラス構文だと new で呼び出さないといけない制限があって自由に this を設定できないので それぞれを new した結果をプロトタイプチェーンでつなげてます
class X {
constructor(){
this.x1 = "x"
}
x(){console.log(this.x1)}
}
class Y {
constructor(){
this.y1 = "y"
}
y(){console.log(this.y1)}
}
const Z = extend(X, Y)
const z = new Z()
z.x()
// x
z.y()
// y
という感じで使えます
ただし 継承対象のクラスが他のクラスを継承してるとうまくいかないことがあります
コンストラクタはそれぞれで呼び出されて継承先も実行されます
メソッドは指定したものしか設定されないので親クラスのものは使えません
extend に親クラスも全部含めればメソッドは使えますが コンストラクタ呼び出しで親クラスのものは自分自身と子クラスのときの 2 度呼び出されるようになります
それと HTMLElement を継承してる場合はエラーになって動きません
こういう複雑なことしてるとクラス構文は自由度が足りなくて扱いづらいなと思いますね