現在策定されているプロポーサルではclassが組み込まれる予定です。 class機能自体はfunctionの糖衣構文になります。
実装済み処理系 : 無し
概要
まずは今までの書き方でクラスを生成してみます。
//このクラスはTHREE.Meshを継承しています。
function SkinnedMesh(geometry, materials) {
//親クラスのコンストラクタを呼び出します。
THREE.Mesh.call(this, geometry, materials);
this.identityMatrix = new THREE.Matrix4();
this.bones = [];
this.boneMatrices = [];
};
//ecmascript5以降での継承です。
//THREE.Mesh.prototypeを継承したprototypeオブジェクトを生成します。
SkinnedMesh.prototype = Object.create(THREE.Mesh.prototype);
SkinnedMesh.prototype.constructor = SkinnedMesh;
SkinnedMesh.prototype.update = function(camera) {
//親クラスのupdateメソッドの呼び出し。
THREE.Mesh.prototype.update.call(this);
};
次にES Nextで予定されているclassを使って書き換えてみます。
//継承を行うためにはclass 'drived' extends 'super'という構文を使う
//superはコンストラクタ関数か、クラスである必要がある
class SkinnedMesh extends THREE.Mesh {
constructor(geometry, materials) {
//親クラスのコンストラクタを呼び出す。
//THREE.Mesh.call(this, geometry, materials)に該当
super(geometry, materials);
//publicなメンバはすべてpublic修飾子を宣言の前に付与する
public identityMatrix = new THREE.Matrix4();
public bones = [];
public boneMatrices = [];
...
}
update(camera) {
...
//親クラスのメソッド呼び出し。
//
super.update();
}
}
次に基本的なクラスの構成要素の説明をします。
class Monster {
//コンストラクタは以下のように定義します。
//クラスのメンバはコンストラクタ内で指定します。
constructor(name, health) {
public name = name;
private health = health;
}
//クラスのメソッドはpublicかprivateのアクセス修飾が可能です。
//指定しない場合はprivateになります。
public attack(target) {
log('The monster attacks ' + target);
}
//省略関数も利用可能です。
public isAlive -> private(this).health > 0;
public health(value) {
if (value < 0) {
throw new Error('Health must be non-negative.')
}
private.health = value
}
//publicな関数以外のメンバはprototypenに設定され、
//全てのインスタンスで共通の値が使用されます。
public numAttacks = 0;
//constなメンバはコンストラクタか、定義時に初期化しなければ
//値の追加はできません。
public const attackMessage = 'The monster hits you!';
//static修飾が宣言されると変数はstaticな変数になります。
//static修飾にはprivate,publicは設定できません。
static const DEFAULT_LIFE = 100;
}
変換後は以下のようになります。
var Monster = function () {
function constructor(name,health) {
this.name = name;
Runtime.getPrivateRecord(this).health = health;
}
function Monster() {
Runtime.initializeClass(this, Monster, _mochaPrivateHolder, constructor, arguments, "Monster", 1);
}
var _mochaPrivateHolder = function (){};
Runtime.createUnenumProp(constructor,"__harmony_class__",1);
Monster.prototype.attack = function (target) {
log('The monster attacks '+target);
};
Monster.prototype.isAlive = function () {
return Runtime.getPrivateRecord(this).health>0;
};
Monster.prototype.health = function (value) {
if (value<0)throw new Error('Health must be non-negative.')
Runtime.getPrivateRecord(this).health = value;
};
Monster.prototype.numAttacks = 0;
Runtime.constant(Monster.prototype,'attackMessage','The monster hits you!');
Runtime.constant(Monster,'DEFAULT_LIFE',100);
Runtime.createUnenumProp(Monster.prototype,"constructor",constructor);
return Monster;
}()
詳細
mochaではclassはコンストラクタ関数と、プロトタイプの糖衣構文として機能します。なので、既存のjavascriptのクラスと100%の互換性を持っています。またクラスを使用することによるオーバーヘッドもできるだけ抑えてありますが、privateフィールドの実現のためにやや実行時にオーバーヘッドがあります。
インターフェース
const(optional) class <class name> extends or prototype <base class name or literal>
クラスを宣言します。class nameとbase class nameはjavascriptで使用可能な変数名を使用可能です。 const修飾子がついていた場合は、すべてのメンバが変更不可能になります。 継承の際にextendsを選ぶと、通常のプロトタイプの継承になります。prototypeを選ぶと、直接<base class name or literal>をprototypeとして使用します。
constructor(<arguments list>) <function body>
コンストラクタを定義します。コンストラクタは必ずconstructorという関数名でなければなりません。 メンバ変数の初期化を行います。コンストラクタが定義されていなければ、mochaが自動で空のコンストラクタを生成します。
const(optional) public or private or static <name> <function body or '=' values>(optional)
メンバを定義します。public修飾子がついていた場合はメンバは外部からアクセス可能になります。 const修飾子がついていた場合は、メンバは変更不可能になります。 private修飾子がついていた場合は、外部からアクセス不可なメンバになります。 何も修飾子を付けなかった場合は、デフォルトでprivateになります。 static修飾子が宣言された場合はメンバはインスタンス無しにアクセス可能になりますが、static以外のメンバにはアクセス出来ません。 またstaticなメンバはprivate、publicといったアクセス修飾は現在は出来ません。
this.<name>
publicメンバにアクセスします。
private(object).<name>
privateメンバにアクセスします。
クラスの振る舞いのみを定義したオブジェクトです。
実装済み処理系 : 無し
概要
traitの定義は以下のように行います。
trait TestTrait {
requires testReq1;
public test1( ...arg )->this.testReq1(arg[0]);
}
trait TestTrait2 {
requires testReq2;
public test1( ...arg )->this.testReq2(arg[0]);
public test2->console.log('ok');
}
class MixinTest {
public testReq1(arg) -> console.log(arg);
public testReq2(arg) -> console.log(arg);
mixin TestTrait with test1 as method1;
mixin TestTrait2 without test1, with test2 as method2;
}
var TestTrait = {
_mochaTraitPrivate : {},
_mochaTraitPublic : {
test1 : function testm1() {
var arg = Runtime.toArray(arguments,0);
this.testReq1(arg[0]);
}
},
_mochaRequires : {
testReq1 : true
},
_mochaTraitMark : true
},
TestTrait2 = {
_mochaTraitPrivate : {},
_mochaTraitPublic : {
test1 : function testm2() {
var arg = Runtime.toArray(arguments,0);
this.testReq2(arg[0]);
},
test2 : function testm3() {
console.log('ok');
}
},
_mochaRequires : {
testReq2 : true
},
_mochaTraitMark : true
},
MixinTest = function () {
function constructor(){}
function MixinTest() {
Runtime.initializeClass(this,MixinTest,_mochaPrivateHolder,constructor,arguments,'MixinTest',101);
}
var _mochaPrivateHolder = function (){};
Runtime.createUnenumProp(constructor,"__harmony_class__",1);
MixinTest.prototype.testReq1 = function (arg) {
return console.log(arg)
};
MixinTest.prototype.testReq2 = function (arg) {
return console.log(arg)
};
Runtime.classMixin(MixinTest,_mochaPrivateHolder,TestTrait, {
test1 : "method1"
},{});
Runtime.classMixin(MixinTest,_mochaPrivateHolder,TestTrait2,{
test2 : "method2"
}, {
test1 : true
});
Runtime.checkRequirements(MixinTest,_mochaPrivateHolder,[TestTrait,TestTrait2],'class_test.js',105);
Runtime.createUnenumProp(MixinTest.prototype,"constructor",constructor);
return MixinTest;
}()
詳細
クラスに対して振る舞いを注入します。traitはrequireプロパティを設定することで実装しなければならないメソッドを指定できます。 またmixin属性を設定することで、trait同士、あるいはclassとtraitを合成可能です。その際にmixin ... with <method_name> as <:new_name>とすることでメソッド名の書き換えが可能です。またwithout属性を指定することで、特定のメソッドのmixinを無効化することが可能です。 public,privateなどの指定はclassを参照してください。
インターフェース
trait <trait name>
traitを宣言します。
mixin <trait name> with(optional) <member name> as <new member name> without(optional) <member name>
traitをmixinします。もし同名のメンバがあった場合は上書きされます。with ~ asが指定されていた場合はmember nameをnew member nameでリネームします。without ~が指定されていた場合はそのメソッドはmixinされません。
分割代入機能です。
実装済み処理系 : mozilla 1.6以降
概要
配列
var array = [0,1,2,3],
[one,two,three,four] = array;
console.log(one,two,three,four);
変換後は以下のようになります。
var array = [0,1,2,3],
one = array[0],
two = array[1],
three = array[2],
four = array[3]
オブジェクト
var obj = {
one : 0,
two : 1,
three : 2,
four : 3
},
{one,two,three,four} = obj;
変換後は以下のようになります。
var obj = {
one : 0,
two : 1,
three : 2,
four : 3
},
one = obj.one,
two = obj.two,
three = obj.three,
four = obj.four;
少々複雑な例
var object = {
value1 : 100,
value2 : {
value3 : 100
},
value4 : [100,200,300],
value5 : {
value6 : [{value7 : 100}]
},
"@value" : {
strvalue : 100
}
}
var {value1,value2:{ value3 },value4 : [ value5_,value6_,value7_ ],value5 : { value6 : [{value7}] },"@value":{strvalue}} = object;
変換後は以下のようになります。
var object = {
value1 : 100,
value2 : {
value3 : 100
},
value4 : [100,200,300],
value5 : {
value6 : [{value7 : 100}]
},
"@value" : {
strvalue : 100
}
},
value1 = object.value1,
value3 = object.value2 && object.value2.value3?object.value2.value3 : undefined,
value5_ = object.value4 && object.value4[0]?object.value4[0] : undefined,
value6_ = object.value4 && object.value4[1]?object.value4[1] : undefined,
value7_ = object.value4 && object.value4[2]?object.value4[2] : undefined,
value7 = object.value5 && object.value5.value6 && object.value5.value6[0] && object.value5.value6[0].value7?object.value5.value6[0].value7 : undefined,
strvalue = object["@value"] && object["@value"].strvalue?object["@value"].strvalue : undefined;
for each構文です。
実装済み処理系 : mozilla 1.6以降
概要
var obj = {a:1,b:2}
for each(var i in obj) {
console.log(i);
}
変換後
var obj = {a:1,b:2}
for(var i in obj) {
i = obj[i];
console.log(i);
}
簡易スコープです。
実装済み処理系 : なし
概要
var x = do { var t = f(); t * t + 1 };
変換後
var x = function () {
var t = f();
return t * t + 1;
}();
Detail
関数の短縮構文です。
実装済み処理系 : なし
概要
無名関数
var a = (x,y,z) -> x + y + z,
b = (x,y,z) -> { return x + y + z; }
変換後は以下のようになります。
var a = function (x,y,z) { return x + y + z;},
b = function (x,y,z) { return x + y + z;}
関数宣言
foo(x,y,z) -> x + y + z;
bar(x,y,z) -> { return x + y + z; }
変換後は以下のようになります。
function foo (x,y,z) { return x + y + z;}
function bar (x,y,z) { return x + y + z;}
コンテキストの束縛
var a = (x,y,z) => x + y + z,
b = (x,y,z) => { return x + y + z; }
foo(x,y,z) => console.log(this);
bar(x,y,z) => {console.log(this) }
変換後は以下のようになります。
var a = function (x,y,z) { return x + y + z; }.bind(this),
b = function (x,y,z) { return x + y + z; }.bind(this);
function foo (x,y,z) { return console.log(_mochaLocalTmp0);}
foo = foo.bind(this);
function bar (x,y,z) { return console.log(_mochaLocalTmp1);}
bar = bar.bind(this);
モジュールのインポート機能です。
実装済み処理系 : なし
概要
import module from './test';
import {exports1,exports2} from './test2'.module;
import [value1, value2] from './test3'.module.exports;
import exports from './test4'.module;
import './test5' as test5
変換後は以下のようになります。
var module = Runtime.modules.get('349045-test.js').module,
_mochaLocalTmp0 = Runtime.modules.get('564059-test2.js').module,
exports1 = _mochaLocalTmp0.exports1,
exports2 = _mochaLocalTmp0.exports2,
_mochaLocalTmp1 = Runtime.modules.get('906045-test3.js').module.exports,
value1 = _mochaLocalTmp1[0],
value2 = _mochaLocalTmp1[1],
exports = Runtime.modules.get('945095-test4.js').module.exports,
test5 = Runtime.modules.get('455905-test5.js');
詳細
importのルール
ファイルのインポートは以下のルールに沿って行われます。
記述 | 説明 |
---|---|
'./filename' | 現在のファイルがあるディレクトリから探します。 |
'filename' | ランタイムモジュール、あるいはoptionのmoduleDirプロパティから探します。 |
モジュール化機能です。
実装済み処理系 : なし
概要
module testModule {
export test() -> console.log('test!!');
}
module testModule2 {
module testInnerModule3 {
export testExport2()->2;
export testExport3 = {test:200};
}
}
module testModule3 = ->3
変換後は以下のようになります。
var testModule = Runtime.modules.get('3490394-test.js').testModule = function () {
function testExport1() {
return console.log('test!!');
}
var _mochaLocalExport = {};
_mochaLocalExport.testExport1 = testExport1;
return _mochaLocalExport;
}();
var testModule2 = Runtime.modules.get('3490394-test.js').testModule2 = function () {
var _mochaLocalExport = {},
testInnerModule3 = _mochaLocalExport.testInnerModule3 = function () {
function testExport2() {
return 2;
}
var _mochaLocalExport = {};
_mochaLocalExport.testExport2 = testExport2;
var testExport3 = _mochaLocalExport.testExport3 = {
test : 200
};
return _mochaLocalExport;
}();
return _mochaLocalExport;
}();
Runtime.modules.get('3490394-test.js').testModule3 = function () {
return 3;
}
詳細
各モジュールはファイルスコープ直下にしか置けません。モジュールに指定した名前が直接エクスポートされます。
宣言
module <module name> { ... }
moduleを宣言します。moduleはネスト可能です。ネストした場合は親のモジュール以下に設定されます。
module <module name> = <values>
直接値をエクスポートします。この構文でモジュールを宣言すると、'='で割り当てた値がmoduleの値になります。
新しいスコープを生成します。
実装済み処理系 : mozilla1.7以降
概要
var foo = 200;
let (foo = 300) {
console.assert(foo === 300);
}
変換後は以下のようになります。
var foo = 200;
!function(foo) {
console.assert(foo === 300);
}(300);
詳細
スコープを生成します。let式の入り口に定義した値はlet式内のスコープでのみ有効になります。
オブジェクトリテラルの拡張構文です。
実装済み処理系 : なし
概要
batch assignment operator
var namespaces = {
foo : {},
bar : {}
}
namespaces.{
baz : {}
}.{
qux : {}
}
変換後は以下のようになります。
var namespaces = {
foo : {},
bar : {}
}
Runtime.extend(Runtime.extend(namespaces, {
bar : {}
}), {
qux : {}
});
private property
var foo = 'private_name';
var namespaces = {
[foo] : {},
bar : {}
}
変換後は以下のようになります。
var foo = 'private_name';
var namespace = {
bar : {}
}
Runtime.createUnenumProp(namespace, 'foo', {});
データ構造の追加構文です。
実装済み処理系 : なし
概要
tuple
var tuple = #[0,1,2,3];
var tuple2 = new Tuple(0,1,2,3);
record
var foo = #{"foo":0,"bar":1,"baz":2}
var bar = new Record({"foo":0,"bar":1,"baz":2});
詳細
tuple
tupleを生成します。tupleは配列に似た値の集合ですが、imutableな値であり、変更不可能です。
API
名前 | インターフェース | 説明 |
---|---|---|
equal | equal(tuple) | 2つのtupleを比較します。 |
toArray | toArray() | 配列に変換します。 |
length | length | tupleの長さを返します。 |
record
オブジェクトに似た連想配列を生成しますが、imutableであり、変更できません。
現在recordには特定のメソッドはありません。
コルーチンや、列挙子を定義する拡張です。
実装済み処理系 : なし
概要
//通常のjavascript
function doCallback(num) {
console.log(num + "\n");
}
function fib() {
var i = 0, j = 1, n = 0;
while (n < 10) {
doCallback(i);
var t = i;
i = j;
j += t;
n++;
}
}
fib();
//generatorを使用すると...
function fib() {
var i = 0, j = 1;
while (true) {
yield i;
var t = i;
i = j;
j += t;
}
}
var g = fib();
for (var i = 0; i < 10; i++) {
console.log(g.next() + "\n");
}
詳細
MDNからの引用
yield キーワードを含む関数がジェネレータです。これを呼ぶと、ジェネレータの仮引数は実引数と結び付きますが、本体は実際には評価されません。代わりにジェネレータ・イテレータが返ってきます。ジェネレータ・イテレータの next() メソッドを呼び出すたびに、繰り返しのアルゴリズムが 1 回ずつ実行されます。それぞれのステップでの値は、yield キーワードで指定された値です。yield をアルゴリズムの繰り返しの範囲を示すジェネレータ・イテレータ版の return だと考えましょう。毎回 next() を呼び出すたび、ジェネレータのコードは yield の次の文から再開します。 あなたはジェネレータ・イテレータを、その next() メソッドを繰り返し呼び出すことで、あなたが望んだ結果の状態にたどりつくまで反復させられます。この例では、私たちが欲しいだけの結果を手に入れるまで g.next() を呼び出し続けることで、私たちはどれだけでも多くのフィボナッチ数を得ることができます。
MDN Generator
簡単に説明すると関数をyieldキーワードの位置で一時停止し、二回目以降はその場所から実行を再開します。またnextだけではなく、yieldに対して、値を与えられるsendメソッドもあります。
MDN
一度 next() メソッドを呼び出してジェネレータをスタートさせると、与えた特定の値を最後の yield の結果として扱わせる send() を使うことができます。その際ジェネレータはその次の yield のオペランドを返します。 ジェネレータを勝手な時点から始めることはできません。特定の値を send() する前に必ず next() でジェネレータをスタートさせなければなりません。
MDN Generator
sendの例
yieldTest()-> {
for ( var i = 0;i<10; i++ ) {
var m = yield i;
if ( m === true ) {
yield i + 1;
} else if ( m === false ){
yield i - 1;
} else {
yield i;
}
}
}
generator = yieldTest();
@assert( true , generator.next() === 0 );
@assert( true , generator.send( true ) === 1 );
@assert( true , generator.send( false ) === 1 );
@assert( true , generator.send( true ) === 2 );
@assert( true , generator.send( false ) === 2 );
@assert( true , generator.send( true ) === 3 );
@assert( true , generator.send( true ) === 3 );
@assert( true , generator.send( true ) === 4 );
@assert( true , generator.send( false ) === 4 );
@assert( true , generator.send( true ) === 5 );
このようにsendに値を与えると、yield i
の部分が引数に置き換わります。
iterator
var iter = {
arr : [],
add : function ( value ) {
this.arr.push( value );
},
iterator : function () {
var arr = this.arr;
return {
index : 0,
next : function () {
if ( arr.length > this.index ) {
var ret = arr[ this.index ];
this.index++;
return ret;
} else {
throw StopIteration;
}
}
}
}
}
詳細
この例のように、iteratorを生成するにはiteratorsモジュールのiteratorの値を、objectのプロパティにする必要があります。 ここで定義したiteratorはES Nextで追加されたfor of構文で使用されます。 詳しくはfor ofを参照してください。
for of構文とは、Generator、あるいはiteratorのnextを呼び出して値を列挙する構文です。
実装済み処理系 : なし
var items = function ( obj ) {
return {
iterator : function () {
for( var i in obj ) {
yield [i, obj[i]];
}
}
}
},
iter = {
arr : [],
add : function ( value ) {
this.arr.push( value );
},
iterator : function () {
var arr = this.arr;
return {
index : 0,
next : function () {
if ( arr.length > this.index ) {
var ret = arr[ this.index ];
this.index++;
return ret;
} else {
throw StopIteration;
}
}
}
}
}
obj = {x:200,y:300}
for of(var i in items(obj)) {
console.log(i[0], i[1]);
}
iter.add( 100 );
iter.add( 200 );
iter.add( 300 );
iter.add( 400 );
for of(var i in iter) {
console.log(i);//100,200,300...
}
generatorかiteratorの定義されたオブジェクトを与えることで、次々にnextを呼び出し、StopIterationが投げられると処理を終了します。
概要
function test (a, b = 1, c = 2) {
return a + b + c;
}
function test2 (a, [b, c]) {
return a + b + c;
}
console.log(test(1))//4
console.log(test2(1,[1,2]))//4
function Point(this._x = 0, this._y = 0) {}
Point.prototype.getX = function () {return this._x;}
Point.prototype.getY = function () {return this._y;}
class Point {
constructor(private(this)._x = 0, private(this)._y = 0) {}
public getX() -> private(this)._x;
public getY() -> private(this)._y;
}
概要
var test = (a, b, c) -> a + b + c;
var TestConstructor = (a, b, c)-> {
this._a = a;
this._b = b;
this._c = c;
}
TestConstructor.prototype.sum = -> this._a + this._b + this._c;
var values = [0,1,2];
console.log(test(...values));//3
var testConstructor = new TestConstructor(...values);
console.log(testConstructor.sum());//3
var [value1, ...value2] = [0,1,2,3];
console.assert(value2[0] == 1 && value2[1] == 2 && value2[2] == 3);
var test = (a, b, ...c)-> a + b + c[0] + c[1];
var values = [1,2];
console.log(test(1,2,values))//6
詳細
概要
var testTarget = {
value1 : 100,
value2 : 200,
value3 : 300
}
var cmpTest = [prop * 2 for each(prop in testTarget)];
@assert( true , cmpTest[ 0 ] === 200 );
@assert( true , cmpTest[ 1 ] === 400 );
@assert( true , cmpTest[ 2 ] === 600 );
var testTarget = {
value1 : 1,
value2 : 2,
value3 : 3,
value4 : 4
}
var cmpTest = [prop for each (prop in testTarget) if (prop % 2 === 0)];
@assert( true , cmpTest[ 0 ] === 2 );
@assert( true , cmpTest[ 1 ] === 4 );
詳細
[<value receiver> for statements ... if statement(opt)]
概要
var testObject = {
value1 : 100,
value2 : 200,
value3 : 300
}
var test = ( x for each ( x in testObject ) );
@assert( true , test.next() === 100 );
@assert( true , test.next() === 200 );
@assert( true , test.next() === 300 );
var testTarget = {
value1 : 1,
value2 : 2,
value3 : 3,
value4 : 4
}
var test = (x for each (x in testTarget) if (x % 2 === 0));
@assert( true , test.next() === 2 );
@assert( true , test.next() === 4 );
詳細
(<value receiver> for statements ... if statement(opt))
概要
if foo === bar {
...
}
for var i = 0; i < 10; i++ {
...
}
for var i in foo {
...
}
for var i of foo {
...
}
while foo {
...
}