IT戦記

プログラミング、起業などについて書いているプログラマーのブログです😚

次の JavaScript の仕様はこうなる! ECMAScript 3.0 から 3.1 への変更点まとめ

はじめに

JavaScript の標準仕様である ECMAScript 3rd Edition (ECMAScript 3.0) が 9 年ぶりにバージョンアップしようとしています。
実は、これまでも様々なバージョンアップの案が上がっては消え、また上がっては消えていました。
しかし、今回のバージョンアップには今までと違う点が一つだけあります。
それは、現時点での主要な ECMAScript インタプリタJavaScript の実行エンジン)を作っている全団体(以下を参照)がこの仕様に同意したことです。

この同意は JavaScript のこれからを大きく変えることでしょう。
この文章では、 ECMAScript 3.1 が標準化されると、 JavaScript実際のコードがどう変わっていくか、何が出来るようになるかに焦点を絞って解説していきたいと思います。
この文章は、 2008 年 8 月 18 日現在のドラフトに基づいて書かれています。
詳細な情報を知りたい場合は、以下のページの PDF を参照してください。
http://wiki.ecmascript.org/doku.php?id=es3.1:es3.1_proposal_working_draft

もくじ

  • Getter と Setter
  • Property Descriptor
  • プロトタイプ(オブジェクトが継承しているオブジェクト)の取得
  • new 以外のプロトタイプ継承
  • for in で走査できないプロパティの取得
  • use subset cautious (strict モード)
  • ブロックスコープ
  • 配列がもっと便利に
  • this の束縛、引数の部分適用
  • JSON のサポート
  • arguments オブジェクトが配列に
  • const 文
  • Decimal オブジェクト

Getter と Setter

ECMAScript 3.1 では、正式に Getter と Setter が仕様化されました。
文法は、 FirefoxSafariOpera が今まで独自に実装していたものと同じになっています。

var obj = {
    value: 1,
    get accessor() {
        return this.value;
    },
    set accessor(value) {
        return this.value = value;
    }
};

obj.accessor = 3;

alert(obj.accessor); // 3

また、オブジェクトリテラル以外の箇所で Getter や Setter を定義したい場合は、 Object.defineProperty を使います。

var obj = { value: 1 };

Object.defineProperty(obj, 'accessor', {
    getter: function() { return this.value } });

Object.defineProperty(obj, 'accessor', {
    setter: function(value) { return this.value = value } });

obj.accessor = 3;

alert(obj.accessor); // 3

Object.defineProperty は、更に様々な用途に使います。その例については、次章の Property Descriptor で解説します。


ちょっと脱線:
Firefox Safari Opera に存在する __defineGetter__ や __defineSetter__ は ECMAScript 3.1 の仕様には存在しませんが、以下のようにすると 同じものを作ることができます。

// __defineGetter__
Object.defineProperty(Object.prototype, '__defineGetter__', {
    enumerable: false,
    value: function(name, fn) {
        Object.defineProperty(this, name, { getter: fn });
    }
});

// __defineSetter__
Object.defineProperty(Object.prototype, '__defineSetter__', {
    enumerable: false,
    value: function(name, fn) {
        Object.defineProperty(this, name, { setter: fn });
    }
});

Property Descriptor

ECMAScript 3.1 からは Property Descriptor というものが使えるようになります。

Property Descriptor の取得

Object.getOwnPropertyDescriptor という関数で Property Descriptor を取得できますので、とりあえず例を見てみましょう。

var obj = {
    hoge: 1,
    get fuga() { return this.hoge },
    set fuga(hoge) { return this.hoge = hoge }
};

// hoge プロパティの情報を取得
var desc = Object.getOwnPropertyDescriptor(obj, 'hoge');

// 値は何か
alert(desc.value);      // 1

// 書き換え可能か
alert(desc.writable);   // true

// for in 文で走査可能か
alert(desc.enumerable); // true

// delete 演算子で削除可能か
// また、 Object.defineProperty 関数で上書き可能か
alert(desc.flexible);   // true

// fuga プロパティの情報を取得
var desc = Object.getOwnPropertyDescriptor(obj, 'fuga');

// Getter として登録されている関数は何か(無ければ undefined )
alert(desc.getter);      // function() { return this.hoge }

// Getter として登録されている関数は何か(無ければ undefined )
alert(desc.getter);      // function(hoge) { return this.hoge = hoge }

// for in 文で走査可能か
alert(desc.enumerable); // true

// delete 演算子で削除可能か
// また、 Object.defineProperty 関数で上書き可能か
// (Setter だけのプロパティに Getter を追加する場合などは上書きじゃない。)
alert(desc.flexible);   // true

このように、プロパティの特性を示すオブジェクトが Property Descriptor です。
プロパティの種類によって Property Descriptor が持つプロパティが変わります。

  • データプロパティ(普通のプロパティ)
    • value
    • writable
    • enumerable
    • flexible
  • アクセサプロパティ(Getter や Setter)
    • getter
    • setter
    • enumerable
    • flexible
Property Descriptor の設定(プロパティの作成)

ECMAScript 3.0 では、プロパティを作る方法は代入(と変数宣言)以外にありませんでした。
しかし、 ECMAScript 3.1 ではオブジェクトに対して Property Descriptor を設定することでプロパティを作ることが出来るようになります。
その方法は、さっきの章(Getter と Setter)で少し紹介した Object.defineProperty を使います。
例を見てみましょう。

var obj = new Object;

// 普通のプロパティ
obj.normal0 = 0;

// 普通のプロパティ
Object.defineProperty(obj, 'normal1', { value: 1 });

// 消せないプロパティ
Object.defineProperty(obj, 'cantDelete', {
    flexible: false,
    value: 2
});

// 上書きできないプロパティ
Object.defineProperty(obj, 'readOnly', {
    writable: false,
    value: 3
});

// for-in 文でイテレーションできないプロパティ
Object.defineProperty(obj, 'dontEnumerable', {
    enumerable: false,
    value: 4
});

// 削除できなくて、上書きできなくて、 for-in 文でイテレーションできないプロパティ
Object.defineProperty(obj, 'concrete', {
    enumerable: false,
    flexible: false,
    writable: false,
    value: 5
});

// 普通の Getter
obj.defineProperty(obj, 'normalGetter', {
    getter: function() {
        return this.value;
    }
});

// 普通の Setter
obj.defineProperty(obj, 'normalSetter', {
    setter: function(value) {
        return this.value = value;
    }
});

// 普通の Getter と Setter
obj.defineProperty(obj, 'normalAccessor', {
    getter: function() {
        return this.value;
    },
    setter: function(value) {
        return this.value = value;
    }
});

// 消せない Getter と Setter
Object.defineProperty(obj, 'cantDeleteAccessor', {
    flexible: false,
    getter: function() {
        return this.value;
    },
    setter: function(value) {
        return this.value = value;
    }
});

// for-in 文でイテレーションできない Getter Setter
Object.defineProperty(obj, 'dontEnumerableAccessor', {
    enumerable: false,
    getter: function() {
        return this.value;
    },
    setter: function(value) {
        return this.value = value;
    }
});

// 消せなくて for-in 文でイテレーションできない Getter Setter
Object.defineProperty(obj, 'concreteAccessor', {
    flexible: false,
    enumerable: false,
    getter: function() {
        return this.value;
    },
    setter: function(value) {
        return this.value = value;
    }
});

ちなみに writable, flexible, enumerable は指定されない場合は、 true と同じように振る舞います。

その他、 Property Descriptor に影響を与える関数群

Object.seal は、オブジェクトのすべてのプロパティ(Own Property:プロトタイプのプロパティを含まないプロパティ)の Property Descriptor の flexible プロパティを false にし、さらに、あらたなプロパティを作れないようにします。

var obj = Object.seal({ hoge: 1 });

obj.hoge = 2;       // OK
delete obj.hoge;    // Error!!
obj.fuga = 1;       // Error!!

Object.freeze は、オブジェクトのすべてのプロパティ(Own Property:プロトタイプのプロパティを含まないプロパティ)の Property Descriptor の flexible, writable プロパティを false にし、さらに、あらたなプロパティを作れないようにします。

var obj = Object.freeze({ hoge: 1 });

obj.hoge = 2;       // Error!!
delete obj.hoge;    // Error!!
obj.fuga = 1;       // Error!!

Object.preventExtensions は、オブジェクトに新たにプロパティを作れないようにします。

var obj = Object.prevent({ hoge: 1 });

obj.hoge = 2;       // OK
delete obj.hoge;    // OK
obj.fuga = 1;       // Error!!

プロトタイプ(オブジェクトが継承しているオブジェクト)の取得

ECMAScript 3.1 では、オブジェクトからプロトタイプを取得する方法が定義されました。その方法は Object.getPrototypeOf という関数を呼び出すことです。

alert(Object.getPrototypeOf([]) === Array.prototype); // true

FirefoxSafari が実装している __proto__ を使って書くと以下のような感じになると思います。

Object.getPrototypeOf = function(obj) {
    if (typeof obj !== 'object' || obj === null) {
        throw new TypeError;
    }
    return obj.__proto__;
};

プロトタイプの動的な代入は出来ないので、 __proto__ の全機能が使えるようになった訳ではありません。

new 以外のプロトタイプ継承

ECMAScript では new 以外のプロトタイプ継承方法が可能になりました。

Object.clone

この関数を使うとオブジェクトをプロトタイプとして継承したオブジェクトを作ることができます。
この関数の特徴は、プロトタイプとして継承するだけではなく、関数呼び出しの性質も受け継ぐことができます。

// new Array とほとんど同じ
var a = Object.clone(Array.prototype);
a.push(1);
alert(a[0]); // 1

Object.clone(function() { alert(1) })(); // 1

ただ、この関数の現段階の仕様ではスコープ(内部プロパティ[[Scope]])が受け継がれないということになっているので、ちょっと不便な気がします。

var a = 1;
(function() {
    var a = 2;
    var f = function() { alret(a) };

    f(); // 2
    Object.clone(f)(); // 1
})();
Object.create

この関数を使うと、オブジェクトをプロトタイプとして継承したオブジェクトを作って、第二引数のオブジェクトのすべてのプロパティ(OwnProperty:プロトタイプのプロパティを含まないプロパティ、 for in で走査できないものも)をマージして返してくれます。
以下のように、クラスの継承的な書き方をするときに便利そうです。

var SuperClass = function() {
    // 略
};
SuperClass.prototype = {
    // 略
};

var SubClass = function() {
    // 略
};
SubClass.prototype = Object.create(SuperClass.prototype, {
    // 略
});

for in で走査できないプロパティの取得

ECMAScript 3.1 では Object.getOwnPropertyNames を使うと、 for in で走査できないプロパティも取得することができます。

// 以下のようにすると今まで見えなかった色々なプロパティが見れます。
alert(Object.getOwnPropertyNames(Object.prototype));

ブロックスコープ

ECMAScript 3.1 では、ブロック内の変数スコープが追加されました。
関数宣言で宣言された関数、 const 文で生成された値、 try 文の catch 節で catch された値にのみがこのスコープの範囲に従います。
以下の三つの例の hogeブロックスコープに従います。

{
    hoge();

    function hoge() {
        alert(1);
    }
}
hoge(); // Error!!
{
    const hoge = 1;

    alert(hoge);
}
alert(hoge); // Error!!
try {
    throw new Error;
}
catch (hoge) {
    alert(hoge);
}
alert(hoge); // Error!!

注)ここの節は、まだまだ仕様が変わる可能性があると思います。

use subset cautious

プログラムの先頭に書くことで、いろいろ制限できるようです。 eval の中に書くこともできます。ちゃんと調べてません。

use subset cautious;

var b;

(function () {
    b = 0; // Error!!
})();

配列がもっと便利に

配列のプロトタイプに色々な関数が追加されました。

  • Array.prototype.indexOf(searchElement [ , fromIndex ])
  • Array.prototype.lastIndexOf(searchElement [ , fromIndex ])
  • Array.prototype.every(callbackfn [ , thisArg ])
  • Array.prototype.some(callbackfn [ , thisArg ])
  • Array.prototype.forEach(callbackfn [ , thisArg ])
  • Array.prototype.map(callbackfn [ , thisArg ])
  • Array.prototype.filter(callbackfn [ , thisArg ])
  • Array.prototype.reduce(callbackfn [ , initialValue ])
  • Array.prototype.reduceRight(callbackfn [ , initialValue ])

全部、名前をどこかで見たことある関数ばかりですね!(説明略)
ググればきっと使い方が出てくることでしょう(ぉぃ

this の束縛、引数の部分適用

this の束縛とか、引数の部分適用するための関数 Function.prototype.bind (thisArg [, arg1 [, arg2, …]]) が追加されました。
prototype.js の bind が仕様になったって感じですね。

var add = function(a, b) {
    return a + b;
};

var succ = add.bind(null, 1);

alert(succ(2)); // 3

JSON のサポート

JSON.parse とか JSON.stringify とかできるようになったみたいです。
色々と出来そうなので、また別のエントリでまとめます。

arguments オブジェクトが配列に

なりました。
正確には、 Array.prototype を継承したオブジェクトになりました。

(function() {
    arguments.forEach(function(arg) {
        alert(arg);
    }, this);
})(1, 2, 3)

ただ、現時点での仕様だと arguments の '0' とか '1' とか数値プロパティの flexible が false なので、 arguments.shift とかが出来ない><

const 文

ブロックスコープで、書き換え不可な変数が定義できるようです。

alert(a); // Error!!

const a = 1;

alert(a); // 1

a = 2;    // Error!!

Decimal オブジェクト

まだ、あまり調べてないです。

// parseInt('1') と同じ
var hoge = new Decimal('1').intValue();

まとめ

ECMAScript 3.1 すてきです
一緒にうきうきうぉっちしませんか?