es6 rev29

  • Assigning to a const declared binding throw, even in non-strict mode code.
  • Eliminated requirement to statically find and report assign to const declared names.
  • Made it a runtime error for a global let/const/class declaration to shadow a non-configurable property of global object.

──だそうです。多少マシになったんじゃないでしょうか。

Object.createはいつ使うのか?


オーバーライドのオーバーライドをしなければいけない時。

つまり伝統的なコードで書くと──

var FooProtoProto = {
// do something
};

function Foo(){

}

Foo.prototype = Object.create(FooProtoProto);
Foo.prototype.f = function(){
}

のような時です。

単純にFoo.prototypeのプロパティを直接オーバーライドするとFooProtoProtoのプロパティを汚染してしまう場合に利用できますが、本格的に利用するのはおそらくjavascriptコードによってinternal methodを実装しなければいけない場面でしょう。

この場合、ただ既存のプロパティに破壊的代入しているだけなのでオーバーライドとは呼びませんが便宜上そう呼ぶことにします。


つまり、このようなコードです。
function CreateStateMachineAbstractOperation(initial){
 // StateMachine Exotic Object

 // 第二引数にStateMachine.prototypeを渡すのはObjectCreate(proto, internalSlotsList)
 // Abstract Operation の4.Set the [[Prototype]] internal slot of obj to proto.に
 // 相当するが直接渡してしまうと問題が起こるので注意[[Construct]]を変更するならそもそも
 // 9.2.3  [[Construct]] ( argumentsList) を正しくエミュレートしなければならない。
 return new Proxy(Object.create(StateMachine.prototype,
 //StateMachine Exotic Object Internal Slots
 {
  __state__: {
   writable: true, enumerable: false, configurable: false,
   value: initial
  }
 }),
 //StateMachine Exotic Object Internal Methods
 {
  set: function(target, P, V, Receiver){
   if(!("update" in V && V.update instanceof Function)) throw new TypeError();
   if(!("accept" in V && V.accept instanceof Function)) throw new TypeError();
   if(!("beforeChange" in V && V.beforeChange instanceof Function)) throw new TypeError();
   if(!("afterChange" in V && V.afterChange instanceof Function)) throw new TypeError();
   target[P] = V;
   return true;
  },
  get: function(target, P, Receiver){
   return target[P];
  },
 });
}


// %StateMachine% intrinsic object and current Realm's StateMachine property.
var StateMachine = new Proxy(function(){}, {
 construct: function(target, argArray){
  return CreateStateMachineAbstractOperation(argArray[0]);
 }
});

// StateMachine prototype object
Object.defineProperties(StateMachine.prototype, {
 update: {
  writable: false, enumerable: false, configurable: false,
  value: function(){
   this.__state__.update(this);
  }
 },
 change: {
  writable: false, enumerable: false, configurable: false,
  value: function(protocol){
   if(this.__state__.accept(protocol)){
    this.__state__.beforeChange(protocol);
    this.__state__ = this[protocol.message];
    this.__state__.afterChange(protocol);
   }
  }
 },
 send: {
  writable: false, enumerable: false, configurable: false,
  value: function(sender, message){
   this.change({"sender": sender, "message": message});
  }
 }
});
見ての通りProxy objectとObject.createによるMOPは冗長でセンスが悪く、はっきり言ってes4のクラス構文に組み込まれたmeta level hookの方がスマートです。
dynamic class MyArray {

    static meta function invoke(...items) {
     return new MyArray(items);
    }

 function MyArray(...items) {
  if (items.length === 1) {
         let item = items[0];
            if (item is AnyNumber) {
             this.length = intrinsic::toUint(item);
         }
        } else {
         this.length = items.length;
            for ( let i=0, limit=items.length ; i < limit ; i++ )
             this[i] = items[i];
  }
 }

    private var __length__: double = 0;

 function get length(): double
  private::__length__;

 function set length(l:double):void
  void(private::__length__ = l);

 override intrinsic function toString(): string
  private::toString();

 private function toString(): string
  "[object MyArray]";

 meta function set(id, value): void {
  let oldLength: double = this.length;
  intrinsic::set(this, id, value);
  this.length = double(id) + 1;
 }
}
──こんな感じです。

functionキーワードの前のstatic,overrideと後ろのget/set以外がシンボルの属する名前空間です(OCamelと違ってシンボルはすべて名前空間に属します)。
meta名前空間に属する関数がmeta level hookと言ってes6ではProxy objectで同じことを実現します。intrinsic::setはabstract operationなんで気にせずに。classキーワードの前のdynamicはというとes4のクラス型は(javaと違ってclassも第一級です)es6と違ってデフォルトで不変なので可変にしたい場合に付けます。

es6でclass構文とProxy objectを併用しても、こうはスッキリと書けないのでMOPをやるときはソースコードのメンテナンス性や可読性にも気をつける必要があります。

class definition completeness issue

以下のコードでは関数C1を前方参照出来ない。

f1;// function f1 binding initialized
C1;// function C1 binding not initialized

function f1(){}
class C1{}

なぜならClassDeclarationには前方参照する仕組みがないからだ。

Declaration [Yield] : See clause 13
HoistableDeclaration [?Yield]
ClassDeclaration [?Yield]
LexicalDeclaration [In, ?Yield]

HoistableDeclaration [Yield, Default] : See clause 13
FunctionDeclaration [?Yield,?Default]
GeneratorDeclaration [?Yield, ?Default]

これは関数定義の完全性に欠けるし何よりソーステキストの読み込みにおいて順序の依存関係を作ってしまう。
これには予期しない暗黙的な順序の依存をもたらす場合がある。例えば、ある関数があるクラスを利用している場合だ。
ライブラリは一般的にブラックボックス化されその詳細を知る必要はないのでこの暗黙的な順序の依存は一般的にライブラリユーザーに解決が難しい問題を与えてしまう。ソーステキストの読み込み手段は処理系依存だが読み込む順序を変えるだけで動いたり動かなかったり挙動が変わってしまうのは混乱の原因になるだろう。

これを一から解決するには利用しているソースコードのすべてを精査しソーステキストの読み込み順序の依存性を把握した上で正しい順序でソーステキストを読み込み直す必要がある。ただし、問題が起った時点ではソーステキストの読み込み順序の依存性が原因だとは判っていないだろう。


個人的にはes6の最大の問題は言語仕様の完全性の低さだと考えているがClassDeclarationに限れば順序に依存する問題のほうが厄介だろう。


より具体例を挙げればモジュールの依存性解決がより複雑になるだろう。実行時の環境依存なソーステキストの読み込み方法に依存してしまうため事前に依存性を定義することができなくなってしまうか、その依存性に順序を含める必要がある。

また伝統的、es5的に書かれたobjectの定義をes6のclass宣言に書き換えると(前方参照できないので)暗黙に非互換が発生してしまうので注意が必要である。

言うまでもなくプログラムの実行はその定義のみに依存するべきであってソーステキストの読み込み順に依存すべきではない。

この問題の根本はhoistingという間違った考え方を言語仕様に持ち込んでしまったことに原因があるのだがhoisting自体の問題はこのブログで散々言ってきたので説明しないが前方参照はただの前方参照でしかない。javascriptは常に2パスで実行されているということを覚えておかなければいけない。