Skip to content

Instantly share code, notes, and snippets.

@teppeis
Last active March 13, 2022 13:57
Show Gist options
  • Save teppeis/06c2b7e97c7d67684c3d3c94159893f3 to your computer and use it in GitHub Desktop.
Save teppeis/06c2b7e97c7d67684c3d3c94159893f3 to your computer and use it in GitHub Desktop.
ES Class Fieldsのプライベートフィールドがハッシュな変態記法なのは何でなんだぜ?

ES Class Fields (Stage 2 now)

プライベートフィールドがハッシュな変態記法なのは何でなんだぜ?

class Point {
    #x;
    #y;
    constructor(x = 0, y = 0) {
        #x = x;
        #y = y;
    }
    get x() { return #x }
    get y() { return #y }
    area() {
        return #x * #y;
        // return this.#x * this.#y;
    }
    equals(p) {
        return #x === p.#x && #y === p.#y;
    }
    toString() {
        return `Point<${ #x },${ #y }>`;
    }
}

そもそもこの仕様の目的は?

  • Encapsulation または hard private がゴール
    • privateフィールドが存在すること自体をクラスの外部から気づかれないようにしたい
    • そうでない(soft private)ならSymbolで事足りるので存在意義がない
  • リフレクションもできない
  • サブクラスからもprivateにアクセスできない
  • オブジェクトの内外からprivateフィールドと同名で別のプロパティを定義可能である必要がある
    • もしエラーになったりするとprivateフィールドの存在がばれてしまう
class Foo {
  #bar = 1;
}
const foo = new Foo();
foo.bar = 2; // Works!

class Bar extends Foo {
  bar = 3; // Works!
}

宣言がprivate fooじゃない理由

  • そのシンタックスの言語はJavaとかだいたい静的型付け言語
  • 動的型付け言語の場合、これをthis.fooと呼び出したときにprivateかどうか不明なのでルックアップが走る
// this.fooが参照されるたびに走る処理系の動きのイメージ(疑似コード)
if (
  otherPoint instanceof Point &&
  isNotSubClass(otherPoint, Point)
) {
  return getPrivate(otherPoint, 'foo');
} else {
  return otherPoint.foo;
}
  • これが 任意のプロパティドットアクセス で走るのは、さすがにやばい
  • よってthis.fooのような従来使われているシンタックスで参照させられない
  • つまり、呼び出し時になんらかの特殊なprefix等が必要なので、宣言時にprivate fooできても嬉しさ半分となる

this['#foo']は禁止

  • breaking changeになるから
  • ドットアクセス限定で

this#fooじゃない理由

  • ASI hazard!
    • ASI: Auto Semi-colon Insersion
    • JSの忌まわしき仕様、自動セミコロン挿入の挙動が壊れる
  • すなわちbreaking change

他の記号じゃダメなの?

# is the most beautiful!

  • @: taken by ES Decorators
  • _, $ : breaking change
  • %, ^, &, ?: ASI hazard
  • non ascii: 打ちづらいでしょ

Why "encapsulation" / "hard private" ?

  1. 見えちゃうとユーザーが使っちゃう、ライブラリ作者は激おこ
  2. すでにclosureやWeakMapでhard privateは実現可能だが、使いづらい
  • closureはclass単位のprivate field を作れない (冒頭コード例のfoo.equals(bar)とか)
  • WeakMapは遅い (筆者注: ほんと?)
  1. 見えるやつなら、Symbolで実現できるしね

hard private以外はDecoratorsを使えばいろいろできるんじゃないか?

結論

  • encapsulationをゴールとする場合、後方互換性、他の仕様との整合性、実装時のパフォーマンスを考慮すると、ほぼ消去法的にthis.#fooしかないという結論

参考

@teppeis
Copy link
Author

teppeis commented Mar 14, 2019

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment