言語に新機能を追加する最良の方法は何でしょうか?この章では、ECMAScript 6 が採用したアプローチについて説明します。これは、バージョニングを回避するため、One JavaScript と呼ばれています。
let
宣言原則として、言語の新しいバージョンは、古くなった機能を削除したり、機能の動作を変更したりすることで、言語を整理する機会となります。これは、新しいコードが言語の古い実装では動作せず、古いコードが新しい実装では動作しないことを意味します。各コードは、言語の特定のバージョンにリンクされます。バージョンが異なる場合の対応として、2つのアプローチが一般的です。
1つ目は、「全部かゼロか」のアプローチを採用し、コードベースが新しいバージョンを使用したい場合、完全にアップグレードする必要があることを要求することです。Python は、Python 2 から Python 3 にアップグレードする際にこのアプローチを採用しました。この問題点は、特に大規模な場合、既存のコードベース全体を一度に移行することが現実的ではない可能性があることです。さらに、このアプローチは、常に古いコードが存在し、JavaScript エンジンが自動的に更新される Web では選択肢になりません。
2つ目は、コードにバージョンをタグ付けすることで、コードベースが複数のバージョンのコードを含むことを許可することです。Web 上では、専用の インターネットメディアタイプ を使用して ECMAScript 6 コードにタグを付けることができます。このようなメディアタイプは、HTTP ヘッダーを介してファイルに関連付けることができます。
Content-Type: application/ecmascript;version=6
また、<script>
要素の type
属性(デフォルト値 は text/javascript
)を介して関連付けることもできます。
<
script
type
=
"application/ecmascript;version=6"
>
···
</
script
>
これは、実際のコンテンツとは別に、帯域外でバージョンを指定します。もう1つの選択肢は、コンテンツ内(帯域内)でバージョンを指定することです。たとえば、ファイルの先頭に次の行を記述します。
use
version
6
;
どちらのタグ付け方法も問題があります。帯域外のバージョンは壊れやすく、失われる可能性があります。帯域内のバージョンはコードに混乱をもたらします。
より根本的な問題は、コードベースごとに複数のバージョンを許可すると、事実上、言語が並行して維持する必要があるサブ言語に分岐することです。これは問題を引き起こします。
したがって、バージョニングは特に JavaScript および Web では避けるべきものです。
しかし、どうすればバージョニングをなくすことができるでしょうか?常に下位互換性を保つことです。つまり、JavaScript を整理することに関する私たちの野心の一部を諦める必要があります。破壊的な変更を導入することはできません。下位互換性があるということは、機能を削除したり、機能を変更したりしないことを意味します。この原則のスローガンは、「Web を壊さない」です。
ただし、新機能を追加したり、既存の機能をより強力にすることはできます。
結果として、新しいエンジンでは、古いコードをすべて実行できるため、バージョンは必要ありません。David Herman は、バージョニングを回避するこのアプローチを、JavaScript を異なるバージョンやモードに分割することを回避するため、One JavaScript (1JS) [1]と呼んでいます。後で見るように、1JS は厳格モードによってすでに存在する分割の一部も元に戻します。
One JavaScript は、言語の整理を完全に諦めなければならないという意味ではありません。既存の機能を整理する代わりに、新しくクリーンな機能を導入します。その一例が、ブロックスコープ変数を宣言する let
であり、var
の改良版です。ただし、var
を置き換えるものではありません。優れたオプションとして、var
と並行して存在します。
いつの日か、もはや誰も使用しない機能を削除することも可能になるかもしれません。ES6 機能の一部は、Web 上の JavaScript コードを調査することで設計されました。2つの例を挙げます。
let
宣言は、let
がそのモードでは予約語ではないため、非厳格モードに追加するのが困難です。有効な ES5 コードのように見える let
の唯一のバリアントは次のとおりです。
let
[
x
]
=
arr
;
調査により、Web 上のコードで、非厳格モードで変数 let
をこの方法で使用するものはなかったことがわかりました。これにより、TC39 は let
を非厳格モードに追加することができました。これがどのように行われたかの詳細は、この章の後半で説明します。
厳格モード は、言語を整理するために ECMAScript 5 で導入されました。ファイルまたは関数内で次の行を最初に記述することで有効になります。
'use strict'
;
厳格モードでは、3種類の破壊的変更が導入されます。
with
ステートメントは禁止されています。これにより、ユーザーは任意のオブジェクトを変数スコープのチェーンに追加できます。これにより、実行速度が低下し、変数が何を参照しているかを把握するのが難しくなります。implements interface let package private protected public static yield
ReferenceError
が発生します。非厳格モードでは、この場合、グローバル変数が作成されます。TypeError
が発生します。非厳格モードでは、単に何の効果もありません。arguments
は、パラメータの現在の値を追跡しなくなりました。this
は、非メソッド関数では undefined
です。非厳格モードでは、グローバルオブジェクト(window
)を参照していたため、new
を指定せずにコンストラクターを呼び出した場合、グローバル変数が作成されました。厳格モードは、バージョニングがなぜトリッキーであるかを示す良い例です。よりクリーンなバージョンの JavaScript を有効にしますが、採用率はまだ比較的低いです。主な理由は、既存のコードの一部が壊れたり、実行速度が低下したり、ファイルに追加するのが面倒である(対話型コマンドラインは言うまでもなく)ためです。私は厳格モードのアイデアが大好きですが、実際にはあまり頻繁に使用していません。
One JavaScript は、スロッピーモードを諦めることはできないことを意味します。スロッピーモードは引き続き存在します(たとえば、HTML 属性内)。したがって、ECMAScript 6 を厳格モードの上に構築することはできません。その機能を厳格モードと非厳格モード(別名スロッピーモード)の両方に追加する必要があります。そうしないと、厳格モードが言語の別のバージョンになり、バージョニングに戻ってしまいます。残念ながら、2つの ECMAScript 6 機能はスロッピーモードに追加するのが困難です。let
宣言とブロックスコープ関数宣言です。その理由と、それらをどのように追加するかを調べてみましょう。
let
宣言 let
を使用すると、ブロックスコープ変数を宣言できます。let
は厳格モードでのみ予約語であるため、スロッピーモードに追加するのが困難です。つまり、次の2つのステートメントは合法的な ES5 スロッピーコードです。
var
let
=
[];
let
[
x
]
=
'abc'
;
厳格な ECMAScript 6 では、予約語 let
を変数名として使用しているため、1行目で例外が発生します。また、2行目のステートメントは、let
変数宣言(デストラクチャリングを使用)として解釈されます。
スロッピーな ECMAScript 6 では、最初の行は例外を発生させませんが、2番目の行は引き続き let
宣言として解釈されます。この識別子 let
の使用方法は Web 上では非常にまれであるため、ES6 はこの解釈をすることができました。let
宣言の他の書き方では、スロッピーな ES5 構文と間違えられることはありません。
let
foo
=
123
;
let
{
x
,
y
}
=
computeCoordinates
();
ECMAScript 5 厳格モードでは、ブロック内の関数宣言は禁止されています。仕様ではスロッピーモードでそれらを許可しましたが、それらがどのように動作すべきかは指定しませんでした。したがって、JavaScript のさまざまな実装でそれらがサポートされていますが、それらを異なる方法で処理します。
ECMAScript 6 では、ブロック内の関数宣言はそのブロックに対してローカルになるようにします。これは ES5 厳格モードの拡張としては問題ありませんが、一部のスロッピーコードを壊します。したがって、ES6 は、ブロック内の関数宣言が関数スコープに存在できるようにする、ブラウザー向けの「Web レガシー互換性セマンティクス」を提供します。
識別子 yield
および static
は、ES5 厳格モードでのみ予約されています。ECMAScript 6 は、コンテキスト固有の構文規則を使用して、それらをスロッピーモードで機能させます。
yield
はジェネレーター関数内でのみ予約語です。static
は現在、クラスリテラル内でのみ使用されています。これは暗黙的に厳格です(下記参照)。ECMAScript 6 では、モジュールとクラスの本体は暗黙的に厳格モードになっています。'use strict'
マーカーは必要ありません。事実上すべてのコードが将来モジュールに存在することを考えると、ECMAScript 6 は事実上言語全体を厳格モードにアップグレードしています。
他の構成要素(アロー関数やジェネレーター関数など)の本体も暗黙的に厳格にすることができました。しかし、これらの構成要素は通常非常に小さいため、スロッピーモードで使用すると、2つのモード間で断片化されたコードになってしまいます。クラス、特にモジュールは、断片化を問題にならないほど十分に大きいです。
One JavaScript の欠点は、既存の癖、特に次の 2 つを修正できないことです。
まず、typeof null
は文字列 'object'
ではなく 'null'
を返す必要があります。TC39 はそれを修正しようとしましたが、既存のコードが壊れてしまいました。一方、新しい種類のオペランドに対する新しい結果を追加することは問題ありません。現在の JavaScript エンジンでは、ホストオブジェクトに対してカスタム値を返すことがあるからです。例として、ECMAScript 6 のシンボルがあります。
> typeof Symbol.iterator
'symbol'
次に、グローバルオブジェクト(ブラウザーの window
)は変数のスコープチェーン内にあるべきではありません。しかし、それを今変更するには遅すぎます。少なくとも、モジュールではグローバルスコープ内にはなく、let
はグローバルスコープで使用した場合でも、グローバルオブジェクトのプロパティを生成することはありません。
ECMAScript 6 では、いくつかの小さな非互換性が発生する変更が導入されています(ただし、遭遇する可能性は低いでしょう)。それらは 2 つの付録に記載されています。
One JavaScript は、ECMAScript 6 を完全に後方互換性のあるものにすることを意味します。それが成功したのは素晴らしいことです。特に、モジュール(したがって、ほとんどのコード)が暗黙的に strict モードになることは高く評価されます。
短期的には、strict モードと sloppy モードの両方に ES6 の構文を追加することは、言語仕様の記述やエンジンでの実装において、より多くの作業が必要です。長期的には、言語がフォークされないことで(肥大化が少なくなるなど)、仕様とエンジンの両方が利益を得ます。プログラマーは、ECMAScript 6 を簡単に使い始めることができるため、One JavaScript からすぐに利益を得られます。
[1] オリジナルの 1JS 提案(注意:古い情報):David Herman による「ES6 doesn’t need opt-in」。