22
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

JavaScript を「純粋な」プロトタイプベースとして使う

Posted at

経緯

JavaScript での開発中、あるオブジェクトの生成で、新しく専用のクラスを作ってインスタンスとするか、既存のクラスからインスタンスを作ってそのプロパティに色々突っ込むか、で悩み、色々調べた所、Io という面白そうな言語を見つけ、ヒントになったと同時にプロトタイプベースに興味も持ったので書きました。

Io っぽく JavaScript を書く

Io とは

io language
公式サイト。

プログラミング言語「Io」の参考情報 - JavaScript勉強会
参考情報をまとめてくれている。

今現在、自分の中で最高の言語です。めっちゃ好き。

実装

まず核となるオブジェクトを作ります。名前は Object が良かったけど被るのでしょうがないです。変数宣言は省略してます。

Stuff = Object.create(null, {
  super: {
    enumerable: true,
    value: null
  }
});

今この Stuff には super 以外に何もデータがありません。試しに

Stuff + ""

を実行してみると、

TypeError: No default value

とのエラーを吐きます。文字列変換時に呼ばれる valueOf メソッドが Stuff に見つからないので当たり前です。では、いろいろメソッドを追加してみます。

// 自分を継承しているオブジェクトを生成するメソッド
Stuff.clone = function() {
  return Object.create(this, {
    super: {
      enumerable: true,
      value: this
    }
  });
};

// プロパティ・メソッドを追加するメソッド
Stuff.assign = function(...args) {
  return Object.assign(this, ...args);
};

// 継承関係を判定するメソッド
Stuff.kindof = function(obj) {
  return this === obj || (!!this.super && this.super.kindof(obj));
};

これでよくある継承やらをやってみます。

A = Stuff.clone();
A.assign({
  hoge() {
    console.log(0);
  }
});

/* new 的なやつ */
a = A.clone();
a.hoge(); // 0

/* extends 的なもの */
B = A.clone();
B.assign({
  /* メソッド追加 */
  fuga() {
    return "fuga";
  },
  /* オーバーライド的なやつ */
  hoge() {
    /* super.hode の呼び出し */
    B.super.hoge.apply(this, arguments);
    console.log(1);
  }
});

b = B.clone();
b.hoge(); // 0 1

b.kindof(A); // true
a.kindof(B); // false

Object.setPrototypeOf などを使った場合でも結局は似たような形になると思います。

この例だと、b を作るのにわざわざ B という「設計図」を作る必要はありません。new 的なやつと extends 的なやつは実質同じことをしており、クラスにするべきかインスタンスにするべきかを考える必要はなくなります。

また、非標準である(ES2015で標準化された?)__proto__ を使えば以下のようにも書けます。

Stuff = {
  __proto__: null
};

A = {
  __proto__: Stuff,
  hoge() {
    console.log(0);
  }
};

B = {
  __proto__: A,
  fuga() {
    return "fuga";
  },
  hoge() {
    B.__proto__.hoge.apply(this, arguments);
    console.log(1);
  }
};

つまり

上の __proto__ を使った例で、オブジェクトの生成・継承に関数を一切使っていないのがわかるでしょうか? つまり、JavaScript では特別なことをせずとも継承が容易に出来るのです。
ここまですっきり書けるのは、JavaScript がそもそもプロトタイプベースだからに他ならず、当たり前といえば当たり前です。Io を真似ることで、JavaScript がプロトタイプベースであることを意識しやすくなったと言えます。

じゃあ JavaScript の「new」とは何なのか

「JavaScript はプロトタイプベースのオブジェクト指向言語である」とよく見ますが、new とか prototype とか __proto__ とか constructor とか、ついには ES2015 の class 構文とか色々な要素がごちゃごちゃしすぎている気がします。

var o = new Foo();

これを呼び出したとき、 JavaScript は、

var o = new Object();
o.[[Prototype]] = Foo.prototype;
Foo.call(o);

実際にはこれ(あるいはこのような何か)を行っており、

o.someProp;

後にこうすると、 osomeProp プロパティを持っているかどうかを調べ、もし持っていなければ Object.getPrototypeOf(o).someProp を、そこにも存在しなければ Object.getPrototypeOf(Object.getPrototypeOf(o)).someProp を…と調べていきます。

継承とプロトタイプチェーン - JavaScript | MDN

そもそも prototype__proto__ が混在しているのが混乱の原因かと思われます。このような状況から、JavaScript をプロトタイプベースと呼んでいいのか議論があるようです。タイトルの「純粋な」もその意味です。

JavaScriptはプロトタイプベースのオブジェクト指向プログラミング言語ではない!? - JavaScript勉強会

「純粋な」プロトタイプベースで書くことについて

メリット

  • シンプル。考えることが少ない。
  • JavaScript はもともとプロトタイプベースなのでそれを引き出しているとも言える。

デメリット

  • ビルトインオブジェクトが JavaScript 式のプロトタイプベースなので整合性が取れない。
  • IDE の型判定などが効かない。

未検証

  • 速度。プロトタイプいじるのは速度的にどうなんだろう。
  • 保守性。結局はうまく設計しないとごちゃごちゃになる。

その他

  • ビルトインオブジェクトとうまくやる方法も考えたい。
  • Proxy とか使ったらもっと面白くなりそう。
  • プロトタイプベースでかける altJS とかあったら面白そう。

結論

  • JS は闇
  • Io はいいぞ
22
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?