ECMAScript 5 の strict モードをJavaScript にオプトインすることによって幾つかの機能を制限します。strict モードは単なるサブセットではありません: strict モードは意図的に、通常モードとは異なる意味を持っています。strict モードをサポートしないブラウザは、strict モードのコードについてサポートするブラウザとは異なる動作をする可能性がありますので、strict モードに関する側面をサポートするかの機能テストを行わずに strict モードを頼らないでください。strict モードのコードと非 strict モードのコードは共存できますので、スクリプトを順次 strict モードにオプトインすることができます。
strict モードでは、通常の JavaScript の意味にいくつかの変更を加えます。第一に strict モードでは、JavaScript でエラーではないが落とし穴になる一部の事柄を、エラーが発生するように変更することで除去します。第二に strict モードでは、JavaScript エンジンによる最適化処理を困難にする誤りを修正します: strict モードのコードは、非 strict モードの同一コードより高速に実行できることがあります (Firefox 4 ではまだ strict モードにあまり最適化していませんが、将来のバージョンで実現するでしょう)。第三に strict モードでは、将来の ECMAScript で定義される予定の構文を禁止します。
strict モードの呼び出し
strict モードはスクリプト全体または個別の関数に適用できます。括弧 {}
で括られるブロック構文には適用できません。そのような場所に適用しようとしても何も起きません。eval
コード、Function
コード、イベントハンドラ属性、setTimeout
コードに渡す文字列、およびこれらに似たものはスクリプト全体であり、strict モードを呼び出すと期待どおりに動作します。
スクリプトに対する strict モード
スクリプト全体で strict モードを呼び出すには、他のいかなる文よりも前に "use strict";
(または 'use strict';
) という文をそのまま追加します。
// Whole-script strict mode syntax "use strict"; var v = "Hi! I'm a strict mode script!";
この構文には、著名なサイトを悩ませた落とし穴があります。それは、競合しないスクリプトをむやみに連結できないことです。strict モードのスクリプトと非 strict モードのスクリプトを連結することを考えてみてください: 連結後のスクリプト全体が strict になるのです! これは逆も言えます: 非 strict のスクリプトと strict のスクリプトを連結すると非 strict になります。strict モードのスクリプト同士の連結や、非 strict モードのスクリプト同士の連結は問題ありません。strict のスクリプトと非 strict のスクリプトの連結により混在すること (crossing the streams) だけが問題になります。従って、(少なくとも移行期間中は) 関数ごとを基準にして strict モードを有効にすることを推奨します。
関数に対する strict モード
同様に、関数で strict モードを呼び出すには、関数本体で他のいかなる文よりも前に "use strict";
(または 'use strict';
) という文をそのまま追加します。
function strict() { // Function-level strict mode syntax 'use strict'; function nested() { return "And so am I!"; } return "Hi! I'm a strict mode function! " + nested(); } function notStrict() { return "I'm not strict."; }
strict モードでの変更点
strict モードでは構文とランタイムの動作の両方に変更を加えます。変更点は主に以下のカテゴリに分類できます: ミスからエラー (構文エラーまたは実行時エラー) への変更、与えられた名前から特定の変数を算出する方法の単純化、eval
および arguments
の単純化、セキュアな JavaScript 作成の容易化、将来の ECMAScript の進化への事前対処。
ミスからエラーへの変換
strict モードでは、従来は受け入れていた一部のミスをエラーに変更します。JavaScript は未熟な開発者にも易しいように設計され、またエラーとすべき操作の一部をエラーとして扱いません。これにより当面の問題を解決したことがありますが、後により大きな問題を引き起こしたこともあります。strict モードではこれらのミスをエラーとして扱うことで、開発者は気づいて修正するようになります。
第一に strict モードでは、偶発的にグローバル変数を作成できないようにします。通常の JavaScript では、代入文で変数名の綴りを間違えるとグローバルオブジェクトに新しいプロパティが作成され、そしてそれは動作し続けます (現在または将来問題になる可能性はありますが)。strict モードでは、代入文で偶発的にグローバル変数を作成せずにエラーを投げます:
"use strict"; mistypedVaraible = 17; // throws a ReferenceError
第二に strict モードでは、代入文で暗黙的に失敗せずに例外が発生するようにします。例えば、NaN
は書き込み不可のグローバル変数です。通常のコードでは NaN
に代入しても何も起きません。つまり、開発者は失敗したという通知を受けません。strict モードでは NaN
に代入すると例外が発生します。通常のコードで暗黙的に失敗する代入 (書き込み不可のプロパティへの代入、getter のみのプロパティへの代入、拡張不可 オブジェクトへの新規プロパティ割り当て) について、strict モードでは例外が発生します:
"use strict"; // Assignment to a non-writable property var obj1 = {}; Object.defineProperty(obj1, "x", { value: 42, writable: false }); obj1.x = 9; // throws a TypeError // Assignment to a getter-only property var obj2 = { get x() { return 17; } }; obj2.x = 5; // throws a TypeError // Assignment to a new property on a non-extensible object var fixed = {}; Object.preventExtensions(fixed); fixed.newProp = "ohai"; // throws a TypeError
第三に strict モードでは、削除できないプロパティを削除しようとするとエラーが発生します (非 strict モードでは何も起きません) :
"use strict"; delete Object.prototype; // throws a TypeError
第四に strict モードでは、オブジェクトリテラル内のプロパティ名はユニークであることを必須にします。通常のコードではプロパティ名が重複してもよく、最後に宣言したものがプロパティの値になります。しかし最後のものだけが有効になることから、例えばプロパティの値を変更するために最後のインスタンス以外を変更する形でコードを書き換える場合など、重複していることがバグになり得ます。strict モードではプロパティ名の重複が構文エラーになります:
"use strict"; var o = { p: 1, p: 2 }; // !!! syntax error
第五に strict モードでは、関数の引数名がユニークであることを必須にします。通常のコードでは、重複した引数がそれより前にある同名の引数を隠します。それら前の引数は arguments[i]
を通して利用できますので、アクセスすることは可能です。しかしこのように隠すことはほとんど意味がなく、またおそらく不要なものであるため (例えば綴りの間違いをわかりにくくするかもしれません)、strict モードでは引数名の重複を構文エラーにします:
function sum(a, a, c) // !!! syntax error { "use strict"; return a + b + c; // wrong if this code ran }
第六に strict モードでは、8 進数表記を禁止します。8 進数表記は ECMAScript の仕様に含まれていませんが、すべてのブラウザは 8 進数表記を先頭にゼロを付加することでサポートします: 0644 === 420
および "\045" === "%"
。未熟な開発者は先頭のゼロに意味がないと考え、それを桁揃えのために用いることがあります。しかし、これでは数値の意味が変わってしまいます! 8 進数表記が役立つことはほとんどなく、また誤って使われかねないため、strict モードでは 8 進数表記を構文エラーにします:
"use strict"; var sum = 015 + // !!! syntax error 197 + 142;
変数の使用の単純化
strict モードでは、コード中の変数名と特定の変数定義との対応づけ方法を単純化します。多くのコンパイラの最適化は、変数 X をあの場所に保管している と表現できることに頼ります: これは JavaScript のコードを最大限に最適化するために重要です。JavaScript ではこのようなコード内の名前と変数定義との基本的な対応づけを、実行時まで行うことができない場合があります。strict モードではこのような事態が起こるケースを取り除くことで、コンパイラが strict モードのコードをより最適化できるようにします。
第一に strict モードでは、with
を禁止します。with
の問題点はブロック内にある名前を、渡されたオブジェクトのプロパティまたはブロックの周囲 (あるいはグローバル) のスコープへ実行時に対応づけることです: これは事前に行うことができません。strict モードでは with
をエラーとすることで、with
内にある名前の指す場所が実行時に不明になる可能性をなくします:
"use strict"; var x = 17; with (obj) // !!! syntax error { // If this weren't strict mode, would this be var x, or // would it instead be obj.x? It's impossible in general // to say without running the code, so the name can't be // optimized. x; }
with
の置き換えとして、オブジェクトに短い名前の変数を割り当てて、その変数を用いて対応するプロパティにアクセスするという代案があります。
第二に strictモードでは、eval
は新しい変数を周囲のスコープに広めません。通常 eval("var x;")
というコードは、変数 x
を周囲の関数やグローバルスコープに広めます。これは通常 eval
の呼び出しを含む関数は、引数やローカル変数を指していないすべての名前を実行時に特定の定義へ対応づけることを意味します (eval
が外部の変数を隠蔽する新たな変数を生成する可能性があるためです)。strict モードでは eval
で評価されているコードでのみ使用できる変数を作成するので、変数名が外部の変数や一部のローカル変数を指しているかにかかわらず eval
は影響を与えません:
var x = 17; var evalX = eval("'use strict'; var x = 42; x"); assert(x === 17); assert(evalX === 42);
関連して、strict モードのコード内で eval(...)
という形式で eval
関数を呼び出した場合は、コードは strict モードとして評価されます。コードを明示的に strict モードで呼び出してもよいですが、必須ではありません。
function strict1(str) { "use strict"; return eval(str); // str will be treated as strict mode code } function strict2(f, str) { "use strict"; return f(str); // not eval(...): str is strict if it invokes strict mode } function nonstrict(str) { return eval(str); // str is strict if and only if it invokes strict mode } strict1("'Strict mode code!'"); strict1("'use strict'; 'Strict mode code!'"); strict2(eval, "'Non-strict code.'"); strict2(eval, "'use strict'; 'Strict mode code!'"); nonstrict("'Non-strict code.'"); nonstrict("'use strict'; 'Strict mode code!'");
従って、strict モードの eval
内にある名前は、eval
の結果として評価されない strict モードのコードと同様に動作します。
第三に strict モードでは、単純名の削除を禁止します。strict モードでは delete name
を構文エラーにします:
"use strict"; eval("var x; delete x;"); // !!! syntax error
eval
および arguments
の単純化
strict モードでは arguments
および eval
の奇妙さを低減します。通常のコードではどちらも不思議な動作がかなりあります: バインドの追加や削除およびバインドする値を変更するための eval
や、arguments
の添字つきプロパティが名前付き引数の別名になることです。strict モードでは eval
および arguments
をキーワードとした手当てにより、完全な修正は将来の ECMAScript まで実現しないものの大きな進歩を遂げます。
第一に、eval
および arguments
という名前に対して言語構文でのバインドや代入を不可にします。以下のような試みはすべて構文エラーになります:
"use strict"; eval = 17; arguments++; ++eval; var obj = { set p(arguments) { } }; var eval; try { } catch (arguments) { } function x(eval) { } function arguments() { } var y = function eval() { }; var f = new Function("arguments", "'use strict'; return 17;");
第二に strict モードのコードでは、内部で作成された arguments
オブジェクトのプロパティがエイリアスになりません。通常のコードでは第一引数 arg
を持つ関数において arg
に値を設定すると arguments[0]
にも設定され、また逆も同様です (引数が提供されない場合や arguments[0]
が削除された場合を除きます)。strict モードの関数での arguments
オブジェクトは、関数が呼び出された当初の引数を保持します。arguments[i]
は対応する名前付き引数の値を追跡せず、また名前付き引数も対応するarguments[i]
の値を追跡しません。
function f(a) { "use strict"; a = 42; return [a, arguments[0]]; } var pair = f(17); console.assert(pair[0] === 42); console.assert(pair[1] === 17);
第三に、arguments.callee
をサポートしません。通常のコードでは、arguments.callee
は取り囲んでいる関数を参照します。この使用法は脆弱です: 取り囲んでいる関数に名前をつけましょう! さらに、arguments.callee
がアクセスされている場合は非インライン関数への参照を提供しなければならないため、arguments.callee
はインライン関数のような最適化を実質的に妨害します。strict モードの関数での arguments.callee
は、書き込みや読み出し時にエラーが発生する、削除不可のプロパティです:
"use strict"; var f = function() { return arguments.callee; }; f(); // throws a TypeError
JavaScript の "セキュア化"
strict モードにより"セキュアな" JavaScript の記述がより簡単になります。現在、一部の Web サイトではユーザ向に対し、Web サイトの他のユーザが実行することができる JavaScript を記述する方法を提供しています。ブラウザ上の JavaScript はユーザの個人的な情報にアクセスできることから、そのような JavaScript は禁じられた機能へのアクセスを削除するよう、実行前に部分的に変換する必要があります。JavaScript の柔軟性は、ランタイムによる多くのチェックなしにこれを行うことを事実上不可能にします。ある言語機能は、ランタイムのチェック実行にかなりパフォーマンスのコストがかかるとして広まっています。strict モードのいくつかの調整、そしてユーザが投稿した JavaScript が strict モードのコードであることや信頼できる方法で呼び出されることの要求により、ランタイムのチェックの必要性をかなり減らします。
第一に strict モードでは、this
として関数に渡された値をオブジェクトへボクシングしません。非ストリクトモードでの関数にとってthis
は常にオブジェクトになります。thisの値は、実行時にthisオブジェクト値として提供されたオブジェクトであったり、真偽値・文字列・数値などのプリミティブな値がthis
として呼び出した時はオブジェクトへボクシングした値、undefined
または null
の this
で呼び出された時はグローバルオブジェクトとなります。 (特定の this
を指定するために call
、apply
、bind
を使用してください)。自動的なボクシングはパフォーマンス上のコストがあり、しかしブラウザでグローバルオブジェクトを公開することは、"セキュアな" JavaScript 環境へのアクセスを提供するグローバルオブジェクトを制限する必要があるためにセキュリティ上の危険性があります。従って strict モードの関数では、指定された this
を変更せずに使用します:
"use strict"; function fun() { return this; } console.assert(fun() === undefined); console.assert(fun.call(2) === 2); console.assert(fun.apply(null) === null); console.assert(fun.call(undefined) === undefined); console.assert(fun.bind(true)() === true);
第二に strict モードでは、ECMAScript の一般的な実装である拡張を通して JavaScript のスタックを "渡り歩く" ことができません。拡張を用いた通常のコードでは中間にある fun
関数を呼び出すと、fun.caller
は fun
を呼び出した直近の関数を指し、また fun.arguments
はその fun
の呼び出しにおける arguments
を指します。これらの拡張は "セキュア化された" コードにで "特権的に" 関数や (潜在的にセキュアでない) 引数へのアクセスを許すことから、"セキュアな" JavaScript に対して問題があります。fun
が strict モードである場合は、fun.caller
および fun.arguments
は書き込みや読み出し時にエラーが発生する、削除不可のプロパティです:
function restricted() { "use strict"; restricted.caller; // throws a TypeError restricted.arguments; // throws a TypeError } function privilegedInvoker() { return restricted(); } privilegedInvoker();
第三に、strict モードの関数での arguments
は対応する関数の呼び出し時の変数にアクセスできません。一部の過去の ECMAScript では arguments.caller
を、プロパティが関数内の変数のエイリアスになるオブジェクトとして実装しました。これには関数の抽象化を通した特権的な値の隠蔽機能を破ることから、セキュリティ上の危険性があります。以上の理由から、今日のブラウザはこれらを実装していません。それでも歴史的な機能性から、strict モードの関数での arguments.caller
は書き込みや読み出し時にエラーが発生する、削除不可のプロパティです:
"use strict"; function fun(a, b) { "use strict"; var v = 12; return arguments.caller; // throws a TypeError } fun(1, 2); // doesn't expose v (or a or b)
将来の ECMAScript への準備
将来の ECMAScript では新たな構文を導入する予定であるため、ECMAScript 5 の strict モードでは移行を容易にする制限事項を適用します。将来の変更点の基礎が strict モードで禁止されていると、変更が容易になります。
第一に strict モードでは、いくつかの識別子を予約語にします。その対象は implements
、interface
、let
、package
、private
、protected
、public
、static
、yield
です。strict モードでは、これらを変数や引数の名前として使用できません。
function package(protected) // !!! { "use strict"; var implements; // !!! interface: // !!! while (true) { break interface; // !!! } function private() { } // !!! } function fun(static) { 'use strict'; } // !!!
Mozilla 特有の注意事項が 2 つあります: 第一に、コードが JavaScript 1.7 以降 (chrome コード、または適切な <script type="">
を使用) かつ strict モードである場合、let
および yield
にはそれらのキーワードが最初に導入されてから持っていた機能があります。しかし <script src="">
や <script>...</script>
で読み込む Web の strict モードのコードでは、let
/yield
を識別子として使用できません。第二に、ES5 では class
、enum
、export
、extends
、import
、super
を無条件に予約していますが、Firefox 5 以前で Mozilla はこれらを strict モードでのみ予約します。
第二に strict モードでは、スクリプトのトップレベルまたは関数内にない function 文を禁止します。通常のコードでは、function 文はどこにでも置くことが許されます。これは ES5 の仕様に (ES3 でさえも) 含まれていません! ブラウザにより意味が異なり互換性がない拡張です。将来の ECMAScript では、おそらくスクリプトのトップレベルや関数内以外での funciton 文に新たな意味を定義します。strict モードではこのような function 文を禁止する ことで将来の ECMAScript の仕様向けの "宣言を取り除きます":
"use strict"; if (true) { function f() { } // !!! syntax error f(); } for (var i = 0; i < 5; i++) { function f2() { } // !!! syntax error f2(); } function baz() // kosher { function eit() { } // also kosher }
このような function 文は基本的な ES5 の拡張であるため、禁止することは strict モードとして適切ではありません。しかしこの動作は ECMAScript 委員会の推奨であり、それゆえブラウザは実装するでしょう。
ブラウザでの strict モード
ブラウザはまだ strict モードを確実に実装していないため、無条件に依存しないでください。strict モードは意味を変えます。それら変更点を当てにすると、strict モードを実装していないブラウザでミスやエラーが発生する可能性があります。strict モードの使用時は注意を払い、また strict モードに関する機能を実装しているかの機能テストにより strict モードへの信頼を補ってください。最後に、strict モードをサポートするブラウザとしないブラウザでのコードのテスト を行うようにしてください。strict モードをサポートしないブラウザでしかテスト行わない場合、strict モードをサポートするブラウザで問題が起きる可能性が高くなります。これは逆の場合も同じです。
参考情報
- Where's Walden? » New ES5 strict mode support: now with poison pills!
- Where's Walden? » New ES5 strict mode requirement: function statements not at top level of a program or function are prohibited
- Where's Walden? » New ES5 strict mode support: new vars created by strict mode eval code are local to that code only
- John Resig - ECMAScript 5 Strict Mode, JSON, and More
- ECMA-262-5 in detail. Chapter 2. Strict Mode.
- Strict mode compatibility table