JavaScript(ECMAScript)の仕様とブラウザ対応状況

最終修正日:2016年10月17日

JavaScriptは、公式な仕様の策定と各ブラウザ・ベンダーの対応状況、さらにフレームワークの仕様を確認した上で、ソフトの利用環境・開発環境に合った機能を選別する必要があり、その調整に大きな労力を割かれます。初めは何を使ってよいのか、勉強してよいのか、分からなくなることも多いと思います。そこで、JavaScriptの仕様策定の流れと、現在の仕様や各ブラウザの対応状況の確認方法、そして過去の変遷と最新動向を整理して紹介します。

JavaScriptの仕様策定の流れ

JavaScriptの仕様統一はECMA(European Computer Manufacturers Association)により行われており、そこではJavaScriptのことをECMAScript(正式名称:ECMA-262)と呼んでいます。TC39という委員会に、主なブラウザ・ベンダーが代表者を派遣して将来のJavaScriptの仕様を議論しています。

現在の仕様や各ブラウザの対応状況の確認方法

JavaScriptの公式な仕様と各ブラウザの対応状況には差があります。機能を先取りするブラウザもあれば、対応が遅れるブラウザもあります。主観的な印象では、Chromeが最も早く対応し、次にFirefox、Safari、Edgeが続き、IEはほとんど対応できていません。ウェブに公開する一般的なサービスでは、現在でも根強く使われているIEに合わせることになるので、最新機能はなかなか使いづらい現状があります。Jqueryなどのフレームワークがその差を埋めてくれる部分もありますが、取捨選択を余儀なくされる場合も多くあります。
正式な現在の仕様は、Standard ECMA-262(英語)で確認できます。
過去の仕様は、
http://www.ecma-international.org/ecma-262/5.1/
http://www.ecma-international.org/ecma-262/6.0/
のようにバージョン番号で整理されています。
公式な仕様を勉強するには、MDN(Mozilla Developer Network) JavaScriptがお勧めです。日本語で学ぶことができますが、少し不明瞭な部分が時々あり、英語の方が正確な表現になっている印象を持ちます。各機能についての主なブラウザの対応状況も記載されています。
各ブラウザの大まかな対応状況は、ウィキペディアのECMAScript(英語)に掲載されています。ECMAScript(日本語)は、更新が遅れがちのようです。
細かな対応状況は、ECMAScript compatibility table(英語)で確認できます。
ちなみに、ブラウザの最新バージョンの確認には、Browse Happy(英語)も使えます。

過去の変遷と最新動向

JavaScriptの仕様は、ブラウザ・ベンダーの主導権争いやウェブの環境変化により、紆余曲折を繰り返してきました。簡単に過去の仕様の変遷を整理します。
・1999年12月に、ECMAScript Edition 3thが策定された後、ECMAScript Edition 3.1thやECMAScript Edition 4thの策定を巡って争いが起き、結局ECMAScript Edition 4thは策定されないまま放棄されました。
・2009年12月には、ECMAScript Edition 3.1thが名称をECMAScript Edition 5thに改められて、小幅な改変に留まる形で策定されました。
・2011年6月には、ECMAScript Edition 5.1thが策定されましたが、これは別の規格組織が策定したISO/IEC Edition 3thとの統一のための仕様で、ECMAScript Edition 5thからの大きな変更はなかったようです。
・2015年6月に、ついに長年の論争の末に、次世代のJavaScriptの仕様として、Class機能などたくさんの新機能を導入したECMAScript Edition 6thが策定されました。
・2016年6月には、ECMAScript Edition 7thが冪乗演算子などいくつかの機能を追加して策定されました。
・2016年10月17日現在は、ECMAScript Edition 8thの策定に向けて審議中。

参照サイト

harmony, ES Wiki
Draft Specification for ES.next (Ecma-262 Edition 6), ES Wiki
Toward Modern Web Apps with ECMAScript 6, Ariya Hidayat, Sencha, 2013/8/13
見えてきた「ECMAScript 6」。JavaScriptの生みの親が書く「Harmony of Dreams Come True」, Publickey 新野淳一, 2012年10月19日
ECMAScript 5 compatibility table, kangax
Standard ECMA-262 5.1 Edition / June 2011, ECMA, June 2011
JavaScript と ECMAScript 仕様, MDN, 2013/05/28 13:01:36
JavaScript の各バージョンにおける新機能, MDN, 2013/05/28 13:01:36
JavaScript, Wikipedia, 2013/05/28 13:01:36
(文中紹介サイト、省略)

最終修正日:2016年10月17日

JavaScriptでclassを継承する

JavaScriptでclassをシュミレートする方法

JavaScriptはprototypeベースのオブジェクト指向で、classという機能は現時点ではありません。
prototypeとは、「原型」という意味です。JavaScriptではあるオブジェクトが他のオブジェクトの原型となることで、オブジェクトの拡張・継承がなされます。一つの具体的な手順としては、オブジェクトのコンストラクタとして関数を利用し、その関数のprototypeプロパティに原型オブジェクトを設定します。
このような手順を取る場合には、関数がオブジェクトを作ったり、繋げたりする機能を果たすということになります。関数のprototypeプロパティには、どのようなオブジェクトでも設定することができますが、「原型」としてのみ利用するオブジェクトを設定することで、関数をclassに見立たてたclassベースのオブジェクト指向をシュミレートすることができます。
classに見立てた関数に、毎回その継承手順を行うのですが、これは継承設定の柔軟性の確保にはなりますが、多少手間がかかります。そこで、基本的な継承手順を自動化した関数を作ります。
継承手順には、大きくプロトタイプチェーン、コンストラクタチェーン、メソッドチェーンがありますが、プロトタイプチェーン関数だけでも作っておくと作業効率が上がります。

以下に、私が作った継承関数を紹介します。プロトタイプチェーンだけではなく、コンストラクタチェーン、メソッドチェーンも楽に作業できるように工夫しました。(メソッドチェーンは、親クラスへの参照があるというだけですが、、。)

関数名: inherit

引数: 親class、子classのコンストラクタ
返値: 子class

説明:

JavaScriptで、classに見立てた関数の継承作業を自動化する関数です。
第一引数に親class、第二引数に子classのコンストラクタを取り、返値として子classを返します。
子classのプロパティ.parentCtorに、親classのコンストラクタが定義されます。これをコンストラクタチェーンに使ってください。
子classのプロパティ.parentに、親classへの参照が定義されます。これをメソッドチェーンに使ってください。

仕様解説

特色

JavaScriptでは、関数がコンストラクタになるので、その関数をclassに見立てると解説しましたが、このclass継承関数inheritでは、classに見立てる関数とは別に、それとセットとなるコンストラクタ関数を用意しました。これによりclass継承作業の柔軟性を確保します。

コスト

子class生成時のinherit実行コールオブジェクト。
子classとセットになるコンストラクタ。上記コールオブジェクトに格納される。

実行フロー

inherit実行時:

子classの定義
プロトタイプチェーン
親classへの参照設定
子classを返す → inherit実行コールオブジェクト残る

子class実行時:

子classに親コンストラクタ(を実行する関数parentCtor、以下同じ)を設定
→ 子class実行コールオブジェクト残る
子classのコンストラクタを実行 → 親コンストラクタを実行
親コンストラクタを廃棄 → 子class実行コールオブジェクト廃棄

使用例:

//親class宣言
function class1(test1,test2){
	this.test1 = test1;
	this.test2 = test2;
}

//子class宣言
var class2 = inherit(class1,function(test1,test3){
//コンストラクタチェーン。引数test2をバインド。
	class2.parentCtor(test1,10); 
	this.test3 = test3;
});

関数のコード:

function inherit(parent,ctor){
        
    //子classを定義する。
    function child(){
        
        if(ctor){
            
            //コンストラクタチェーンをchildにセットする。
            var itself = this;
            child.parentCtor = function(){
                parent.apply(itself,arguments);
            }
            
            //コンストラクタを実行する。
            ctor.apply(this,arguments);
            
            //実行済みの親コンストラクタを廃棄する。
            delete child.parentCtor;
            
        }
    }
    
    //プロトタイプチェーン。
    function temClass(){} //プロトタイプのみ継承するため。
    temClass.prototype = parent.prototype;
    child.prototype = new temClass();
    //constructorプロパティを修正する
    child.prototype.constructor = child;
    
    //メソッドチェーンのため、親classへの参照を格納。
    child.parent = parent;
    
    return child;
};

動作確認用コード:

以下のコードは、jsdo.itで試行できます。
親class1、子class2、孫class3

//class関数の定義
var Utility = {};
Utility.inherit = function(parent,ctor){
	function child(){
		if(ctor){
			var itself = this;
			child.parentCtor = function(){
				parent.apply(itself,arguments);
			}
			ctor.apply(this,arguments);
			delete child.parentCtor;
		}
	}
	function temClass(){}
	temClass.prototype = parent.prototype;
	child.prototype = new temClass();
	child.prototype.constructor = child;
	child.parent = parent;
	return child;
};

//親class1の定義
function class1(test1,test2){
	this.test1 = test1;
	this.test2 = test2;
}
//親class1のメソッド定義
class1.prototype.display1 = function(){
	alert(this.test1);
};
class1.prototype.display2 = function(){
	alert(this.test2);
};

//子class2の定義
var class2 = Utility.inherit(class1,function(test1,test3){
	//コンストラクタチェーン。引数test2をバインド。
	class2.parentCtor(test1,10); 
	this.test3 = test3;
});
//子class2のメソッド定義
class2.prototype.display3 = function(){
	alert(this.test3);
};

//孫class3の定義
var class3 = Utility.inherit(class2,function(test1){
	//コンストラクタチェーン。引数test3をバインド。
	class3.parentCtor(test1,5);
});

//子class2のオブジェクト生成
var class2Obj = new class2(1,2);
//テスト、メソッド実行
class2Obj.display1();	//1
class2Obj.display2();	//10
class2Obj.display3();	//2

//孫class3のオブジェクト生成
var class3Obj = new class3(1);
var class3Obj2 = new class3(2);
//テスト、メソッド実行、class3Obj
class3Obj.display1();	//1
class3Obj.display2();	//10
class3Obj.display3();	//5
//テスト、メソッド実行、class3Obj2
class3Obj2.display1();	//2
class3Obj2.display2();	//10
class3Obj2.display3();	//5

参考文献:

JavaScript 第5版, David Flanagan, 訳:村上 列, 株式会社オライリー・ジャパン, 2009年3月16日
クラスの継承の仕方 – JavaScript – 教えて!goo, http://oshiete.goo.ne.jp/qa/1617352.html, 2005年8月31日