Strict モード

デフォルトの厳格でないモードをSloppy モードと呼ぶのを目にすることがあるかもしれません。これは公式な用語ではありません、念のため注意してください。

ECMAScript 5 で導入された Strict モードは JavaScript にオプトインすることによって幾つかの機能を制限する方法であり、それによって暗黙のうちに "Sloppy モード" からオプトアウトすることができます。Strict モードは単なるサブセットではなく、通常モードとは意図的に異なる意味を持っています。Strict モードをサポートしないブラウザーは、Strict モードをサポートするブラウザーとは異なる動作をする可能性がありますので、Strict モードに関する側面をサポートするかの機能テストを行わずに Strict モードを頼らないでください。Strict モードのコードと非 Strict モードのコードは共存できますので、スクリプトを順次 Strict モードにオプトインすることができます。

Strict モードでは、通常の JavaScript の意味にいくつかの変更を加えます。

  1. エラーではないが落とし穴になる一部の事柄を、エラーが発生するように変更することで除去します。
  2. JavaScript エンジンによる最適化処理を困難にする誤りを修正します。Strict モードのコードは、非 Strict モードのコードより高速に実行できる可能性があります。
  3. 将来の ECMAScript で定義される予定の構文の使用を禁止します。

JavaScript の制限付きバリアントで機能するようにコードを変更する場合は、厳格モードへの移行を参照してください。

Strict モードの呼び出し

Strict モードはスクリプト全体または個別の関数に適用できます。括弧 {} で括られるブロック構文には適用できません。そのような場所に適用しようとしても何も起きません。eval コード、Function コード、イベントハンドラ属性、setTimeout コードに渡す文字列、およびこれらに似たものはスクリプト全体であり、Strict モードを呼び出すと期待どおりに動作します。

スクリプトでの Strict モード

スクリプト全体で Strict モードを呼び出すには、他のいかなる文よりも前に "use strict"; (または 'use strict';) という文をそのまま追加します。

// スクリプト全体の Strict モード構文
'use strict';
var v = "こんにちは! Strict モードのスクリプト!";

この構文には、著名なサイト悩ませた落とし穴があります。それは、競合しないスクリプトをむやみに連結できないことです。Strict モードのスクリプトと非 Strict モードのスクリプトを連結することを考えてみてください。連結後のスクリプト全体が strict になるのです! これは逆も言えます。非 Strict のスクリプトと Strict のスクリプトを連結すると非 Strict になります。もちろん、スクリプトの連結は決して理想的なものではありませんが、どうしても必要な場合は、機能ごとに Strict を有効にすることを検討してください。

スクリプトの内容全体を関数でラップし、その外側の関数で Strict モードを使用するという方法もあります。これにより連結の問題が解消され、共有変数を関数スコープから明示的にエクスポートする必要があります。

関数での Strict モード

同様に、関数で Strict モードを呼び出すには、関数本体で他のいかなる文よりも前に "use strict"; (または 'use strict';) という文をそのまま追加します。

function strict() {
  // 関数レベルの Strict モード構文
  'use strict';
  function nested() { return '私もそうです!'; }
  return "こんにちは! Strict モードの関数です!  " + nested();
}
function notStrict() { return "Strict モードではありません"; }

モジュールでの Strict モード

ECMAScript 2015 では、JavaScript モジュールが導入されたため、Strict モードに入るための第3 の方法が導入されました。JavaScript モジュールの内容全体が自動的に Strict モードになり、それを開始するための宣言は必要ありません。

function strict() {
    // これはモジュールなので、既定で Strict モードです
}
export default strict;

Strict モードでの変更点

Strict モードでは構文とランタイムの動作の両方に変更を加えます。変更点は主に以下のカテゴリに分類できます: ミスからエラー (構文エラーまたは実行時エラー) への変更、与えられた名前から特定の変数を算出する方法の単純化、eval および arguments の単純化、セキュアな JavaScript 作成の容易化、将来の ECMAScript の進化への事前対処。

ミスからエラーへの変換

Strict モードでは、従来は受け入れていた一部のミスをエラーに変更します。JavaScript は未熟な開発者にも易しいように設計され、またエラーとすべき操作の一部をエラーとして扱いません。これにより当面の問題を解決したことがありますが、後により大きな問題を引き起こしたこともあります。Strict モードではこれらのミスをエラーとして扱うことで、開発者は気づいて修正するようになります。

第一に、Strict モードでは、偶発的にグローバル変数を作成できないようにします。通常の JavaScript では、代入文で変数名の綴りを間違えるとグローバルオブジェクトに新しいプロパティが作成され、そしてそれは動作し続けます (現在または将来問題になる可能性はありますが)。Strict モードでは、代入文で偶発的にグローバル変数を作成せずにエラーを投げます:

'use strict';
                       // グローバル変数 mistypeVariable が存在しないと仮定すると
mistypeVariable = 17;  // この行は変数のスペルミスによる参照エラーを投げます。

第二に、Strict モードでは、代入文で暗黙的に失敗せずに例外が発生するようにします。例えば、NaN は書き込み不可のグローバル変数です。通常のコードでは NaN に代入しても何も起きません。つまり、開発者は失敗したという通知を受けません。Strict モードでは NaN に代入すると例外が発生します。通常のコードで暗黙的に失敗する代入 (書き込み不可のプロパティへの代入、getter のみのプロパティへの代入、拡張不可 オブジェクトへの新規プロパティ割り当て) について、Strict モードでは例外が発生します:

'use strict';

// 書き換え不可能なグローバルへの代入
var undefined = 5; // TypeError を投げます
var Infinity = 5; // TypeError を投げます

// 書き換え不可能なプロパティへの代入
var obj1 = {};
Object.defineProperty(obj1, 'x', { value: 42, writable: false });
obj1.x = 9; // TypeError を投げます

// ゲッター専用プロパティへの代入
var obj2 = { get x() { return 17; } };
obj2.x = 5; // TypeError を投げます

// 拡張不可能なオブジェクトの新しいプロパティへの代入
var fixed = {};
Object.preventExtensions(fixed);
fixed.newProp = 'ohai'; // TypeError を投げます

第三に、Strict モードでは、削除できないプロパティを削除しようとするとエラーが発生します。(非 Strict モードでは何も起きません)

'use strict';
delete Object.prototype; // TypeError を投げます

第四に、Strict モードでは、オブジェクトリテラル内のプロパティ名は一意であることを必須にします。通常のコードではプロパティ名が重複してもよく、最後に宣言したものがプロパティの値になります。しかし最後のものだけが有効になることから、例えばプロパティの値を変更するために最後のインスタンス以外を変更する形でコードを書き換える場合など、重複していることがバグになり得ます。Strict モードではプロパティ名の重複が構文エラーになります。

ECMAScript 2015 では、このようなことはなくなりました。 (bug 1041128).

'use strict';
var o = { p: 1, p: 2 }; // !!! 構文エラー

第五に、Strict モードでは、関数の引数名が一意であることを必須にします。通常のコードでは、重複した引数がそれより前にある同名の引数を隠します。それら前の引数は arguments[i] を通して利用できますので、アクセスすることは可能です。しかしこのように隠すことはほとんど意味がなく、またおそらく不要なものであるため (例えば綴りの間違いをわかりにくくするかもしれません)、Strict モードでは引数名の重複を構文エラーにします。

function sum(a, a, c) { // !!! 構文エラー
  'use strict';
  return a + a + c; // このコードが実行されると
}

第六に、ECMAScript 5 の Strict モードでは、8 進数表記を禁止します。8 進数表記は ECMAScript 5 の仕様に含まれていませんが、8 進数の前にゼロを付けることで、すべてのブラウザーでサポートされます: 0644 === 420 および "\045" === "%" のように。ECMAScript 2015 では、8 進数は数字の前に "0o" を付けることでサポートされます。

var a = 0o10; // ES2015: 8 進数

未熟な開発者は先頭のゼロに意味がないと考え、それを桁揃えのために用いることがあります。しかし、これでは数値の意味が変わってしまいます。8 進数表記が役立つことはほとんどなく、また誤って使われかねないため、Strict モードでは構文エラーになります。

'use strict';
var sum = 015 + // !!! 構文エラー
          197 +
          142;

var sumWithOctal = 0o10 + 8;
console.log(sumWithOctal); // 16

第七に、ECMAScript 2015 の Strict モードでは、プリミティブ値にプロパティを設定することが禁止されます。非 Strict モードではプロパティの設定が単純に無視され、Strict モードでは TypeError が投げられます。

(function() {
'use strict';

false.true = '';         // TypeError
(14).sailing = 'home';   // TypeError
'with'.you = 'far away'; // TypeError

})();

変数の使用の単純化

Strict モードでは、コード中の変数名と特定の変数定義との対応づけ方法を単純化します。多くのコンパイラの最適化は、変数 Xあの場所に保管している と表現できることに頼ります: これは JavaScript のコードを最大限に最適化するために重要です。JavaScript ではこのようなコード内の名前と変数定義との基本的な対応づけを、実行時まで行うことができない場合があります。Strict モードではこのような事態が起こるケースを取り除くことで、コンパイラが Strict モードのコードをより最適化できるようにします。

第一に、Strict モードでは、with を禁止します。with の問題点はブロック内にある名前を、渡されたオブジェクトのプロパティまたはブロックの周囲 (あるいはグローバル) のスコープへ実行時に対応づけることです: これは事前に行うことができません。Strict モードでは with をエラーとすることで、with 内にある名前の指す場所が実行時に不明になる可能性をなくします。

'use strict';
var x = 17;
with (obj) { // !!! 構文エラー
  // Strict モードでなかったら、これは var x になるのでしょうか、
  // それとも obj.x になるのでしょうか?
  // コードを実行してみないと一概には言えないので、
  // 名前を最適化することはできません。
  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;");
console.assert(x === 17);
console.assert(evalX === 42);

関連して、Strict モードのコード内で eval(...) という形式で eval 関数を呼び出した場合は、コードは Strict モードとして評価されます。コードを明示的に Strict モードで呼び出してもよいですが、必須ではありません。

function strict1(str) {
  'use strict';
  return eval(str); // str は Strict モードのコードとして扱われます。
}
function strict2(f, str) {
  'use strict';
  return f(str); // not eval(...): str は Strict モードを
                 // 呼び出した場合のみ Strict です。
}
function nonstrict(str) {
  return eval(str); // str は Strict モードを
                    // 呼び出した場合のみ Strict です。
}

strict1("'Strict モード!'");
strict1("'use strict'; 'Strict モード!'");
strict2(eval, "'Strict モードではない'");
strict2(eval, "'use strict'; 'Strict モード!'");
nonstrict("'Strict モードではない'");
nonstrict("'use strict'; 'Strict モード!'");

従って、Strict モードの eval 内にある名前は、eval の結果として評価されない Strict モードのコードと同様に動作します。

第三に、Strict モードでは、単純名の削除を禁止します。Strict モードでは delete name を構文エラーにします:

'use strict';

var x;
delete x; // !!! 構文エラー

eval('var y; delete y;'); // !!! 構文エラー

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(); // TypeError が投げられます

JavaScript の "セキュア化"

Strict モードにより"セキュアな" JavaScript の記述がより簡単になります。現在、一部の Web サイトではユーザー向に対し、Web サイトの他のユーザーが実行することができる JavaScript を記述する方法を提供しています。ブラウザー上の JavaScript はユーザーの個人的な情報にアクセスできることから、そのような JavaScript は禁じられた機能へのアクセスを削除するよう、実行前に部分的に変換する必要があります。JavaScript の柔軟性は、ランタイムによる多くのチェックなしにこれを行うことを事実上不可能にします。ある言語機能は、ランタイムのチェック実行にかなりパフォーマンスのコストがかかるとして広まっています。Strict モードのいくつかの調整、そしてユーザーが投稿した JavaScript が Strict モードのコードであることや信頼できる方法で呼び出されることの要求により、ランタイムのチェックの必要性をかなり減らします。

第一に、Strict モードでは、this として関数に渡された値をオブジェクトへボクシングしません。非ストリクトモードでの関数にとって this は常にオブジェクトになります。this の値は、実行時に this オブジェクト値として提供されたオブジェクトであったり、真偽値・文字列・数値などのプリミティブな値が this として呼び出した時はオブジェクトへボクシングした値、undefined または nullthis で呼び出された時はグローバルオブジェクトとなります。(特定の this を指定するために callapplybind を使用してください)。自動的なボクシングはパフォーマンス上のコストがあり、しかしブラウザーでグローバルオブジェクトを公開することは、"セキュアな" 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 モード関数の中で、this を介して window オブジェクトを参照することができなくなったことを意味します。

第二に、Strict モードでは、ECMAScript の一般的な実装である拡張を通して JavaScript のスタックを "渡り歩く" ことができません。拡張を用いた通常のコードでは中間にある fun 関数を呼び出すと、fun.callerfun を呼び出した直近の関数を指し、また fun.arguments はその fun の呼び出しにおける arguments を指します。これらの拡張は "セキュア化された" コードにで "特権的に" 関数や (潜在的にセキュアでない) 引数へのアクセスを許すことから、"セキュアな" JavaScript に対して問題があります。fun が Strict モードである場合は、fun.caller および fun.arguments は書き込みや読み出し時にエラーが発生する、削除不可のプロパティです:

function restricted() {
  'use strict';
  restricted.caller;    // TypeError が投げられます
  restricted.arguments; // 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; // TypeError が投げられます
}
fun(1, 2); // v を公開しません(または a または 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 では classenumexportextendsimportsuper を無条件に予約していますが、Firefox 5 以前で Mozilla はこれらを Strict モードでのみ予約します。

第二に、Strict モードでは、スクリプトのトップレベルまたは関数内にない function 文を禁止します。通常のコードでは、function 文はどこにでも置くことが許されます。これは ES5 の仕様に (ES3 でさえも) 含まれていません! ブラウザーにより意味が異なり互換性がない拡張です。将来の ECMAScript では、おそらくスクリプトのトップレベルや関数内以外での funciton 文に新たな意味を定義します。Strict モードではこのような function 文を禁止する ことで将来の ECMAScript の仕様向けの "宣言を取り除きます":

'use strict';
if (true) {
  function f() { } // !!! 構文エラー
  f();
}

for (var i = 0; i < 5; i++) {
  function f2() { } // !!! 構文エラー
  f2();
}

function baz() { // kosher
  function eit() { } // also kosher
}

このような function 文は基本的な ES5 の拡張であるため、禁止することは Strict モードとして適切ではありません。しかしこの動作は ECMAScript 委員会の推奨であり、それゆえブラウザーは実装するでしょう。

ブラウザーでの Strict モード

ブラウザーはまだ Strict モードを確実に実装していないため、無条件に依存しないでください。Strict モードは意味を変えます。それら変更点を当てにすると、Strict モードを実装していないブラウザーでミスやエラーが発生する可能性があります。Strict モードの使用時は注意を払い、また Strict モードに関する機能を実装しているかの機能テストにより Strict モードへの信頼を補ってください。最後に、Strict モードをサポートするブラウザーとしないブラウザーでのコードのテスト を行うようにしてください。Strict モードをサポートしないブラウザーでしかテスト行わない場合、Strict モードをサポートするブラウザーで問題が起きる可能性が高くなります。これは逆の場合も同じです。

仕様

仕様書
ECMAScript (ECMA-262)
Strict Mode Code の定義

関連情報