“use strict”(厳格モード)を使うべきか?

2013年02月20日

カテゴリー:

JavaScriptコード内で”use strict”宣言を使用すると、そのコードは「strictモード(厳格モード)」で実行されるようになります。

strictモードでは、より的確なエラーチェックが行われるため、これまでエラーにならなかったような曖昧な実装がエラー扱いになります。このことにより、コード内に存在する潜在的な問題を早期に発見しやすくなります。また、JavaScriptエンジンによる最適化処理を困難にする誤りを修正するため、strictモードのコードは非strictモードの同一コードよりも高速に実行することができる場合があるなどのメリットがあります。

JavaScriptはECMAScript(ES)標準というものを土台にしていますが、strictモードが使用できるのはESバージョン5からとなります。
ESバージョン5は2009年12月に採択されたものですのでstrictモードも誕生してから4年程経つことになりますが、JavaScriptにおけるstrictモードの利用はまだまだ浸透しきっていないように思えます。(私自身、最近まで使っていませんでした。)

また、ES5自体、次のバージョンに備えるための過渡的なバージョンという意味合いが強いらしく、開発者はstrictモードで動くコードを書くように推奨されてはいますが、強制されてはいません。(JSLintではstrictモードを推奨しているようです)

それと、strictモードを使用するにあたっては、ブラウザの実装状況なども意識しておく必要がありそうです。
そのあたりも踏まえて、現状、strictモードを使用すべきか否かを検討してみたいと思います。


本エントリをまとめるにあたって以下の記事を参考にさせて頂きました。

■Strict モード – JavaScript | MDN
https://developer.mozilla.org/ja/docs/JavaScript/Reference/Functions_and_function_scope/Strict_mode

■Can I use ECMAScript 5 Strict Mode?
http://caniuse.com/use-strict

strictモードと非strictモードの違い

strictモードでは、従来の非strictモードからいくつかの変更点があります。
変更点の詳細については、こちらを参照していただくとして、簡単に概要をまとめると、下記のような違いがあります。

ミスからエラーへの変換

従来は受け入れていた一部のミスをエラーに変更します。これにより開発者はよりエラーに気づきやすくなります。

非strict strict
宣言されていない変数への代入 グローバル変数が作成される エラー
書込み不可の変数への代入
書き込み不可プロパティへの代入
何も起きない エラー
削除不可のプロパティの削除 何も起きない エラー
プロパティ名の重複 最後に宣言したものがプロパティの値となる エラー
関数の引数名の重複 重複した引数がそれより前にある同名の引数を隠す。
(arguments[]プロパティを通じてアクセスすることは可能)
エラー
8進数標記 8進数として処理される エラー

変数の使用の単純化

  1. withの使用の禁止
    strictモードではwith文が使えなくなります。代替の手段としては、オブジェクトに短い名前の変数を割り当てて、その変数を用いて対応するプロパティにアクセスすることができます。
  2. eval内で宣言された変数のスコープ
    非strictモードでは、eval("var x ;")の様に、eval関数内で定義された変数のスコープは、evalが含まれるスコープ(関数、またはグローバルスコープ)まで広がりますが、strictモードでは、eval内で定義された変数を周囲のスコープに広げません。
  3. 単純名の削除の禁止
    strict モードでは delete name を構文エラーにします

evalおよびargumentsの単純化

  1. strictモードでは「eval」「arguments」という名前がキーワードとして扱われるため、これらのキーワードに対して言語構文でのバインドや代入を不可にします。
    例えば、var eval = 1; のような記述は構文エラーになります。
  2. strictモードでは、関数でのarguments オブジェクトは、関数が呼び出された当初の引数を保持します。
    非strictのモードでは第一引数 arg を持つ関数において arg に値を設定すると arguments[0] にも設定され、また逆も同様です (引数が提供されない場合や arguments[0] が削除された場合を除きます)。
    strictモードでは、arguments[i] は対応する名前付き引数の値を追跡せず、また名前付き引数も対応するarguments[i] の値を追跡しません。
  3. strictモードでは、arguments.callee をサポートしません。代替として arguments.callee が参照する関数に名前を付けて呼び出すようにしましょう。

JavaScriptのセキュア化

  1. 非strictモードでは、thisとして関数に渡された値は、それがオブジェクトであれ、または数値や文字列であったとしても、関数内ではthisはオブジェクト(ラッパーオブジェクト)として扱われます。
    つまり、thisとして文字列を指定した場合は、関数内ではthisがStringオブジェクトになり、数値ならばNumberオブジェクトになります。
    一方、strictモードでは、thisとして関数に渡された値は、オブジェクトに閉じ込められることなく、そのまま関数に渡されます。
    function foo() {
        window.console.log(typeof this);
    }
    
    function bar() {
        "use strict";
        window.console.log(typeof this);
    }
    
    foo.call(123); //object
    bar.call(123); //number
    
  2. strictモードでは、ECMAScriptの一般的な実装である拡張を通してJavaScriptのスタックを”渡り歩く”ことができません具体的には以下を指します。
    • strictモードでは、関数またはargumentsオブジェクトのcallerプロパティを使用できません。
    • strictモードでは、関数のargumentsプロパティを使用できません。(argumentsキーワードは使用できる)

将来のECMAScriptへの準備

将来のECMAScriptでは新たな構文を導入する予定であるため、ECMAScript5のstrictモードでは移行を容易にする制限事項を適用します。将来の変更点の基礎がstrictモードで禁止されていると、変更が容易になります

  1. strict モードでは、いくつかの識別子を予約語にします。その対象は implements、interface、let、package、private、protected、public、static、yieldです。strict モードでは、これらを変数や引数の名前として使用できません。
  2. strict モードでは、スクリプトのトップレベルまたは関数内(のトップレベル)にないfunction文を禁止します。
    非strictモードでは、function文はどこにでも置くことが許されます。しかし、これはES5の仕様に(ES3でさえも)含まれていない、ブラウザにより意味が異なり互換性がない拡張です。
    "use strict";
    
    if (true) {
        function foo() {}  //!!! syntax error
        foo();
    }
    

    ただし、上記のような「関数宣言」ではなく、関数式(無名関数)に関してはどこで使用しても問題無いようです。

    "use strict";
    
    if (true) {
        var foo = function() {
            console.log("foo");  //foo
        };
    
        foo();
    
        (function() {
            console.log("bar");  //bar
        }());
    }
    

strictモード準拠のコードの非strictモードでの挙動(互換性)

strictモードはES5で採択された比較的新しいものなので、まだまだstrictモードに対応していないブラウザなどもあります。そういったブラウザでは、strictモード準拠のコードも、非strictモードで動作することになります。

strictモードでは、strictモードであることを宣言する"use strict"文以外には、新たな構文などはありません。

また、この”use strict”文は、非strictモード下では、単なる無意味な文字列でしかないため、無視されるだけでコードの動作には何も影響を与えません。

つまり、非strictモード側から見たら、strictモード準拠のコードは新しいものは何もなく、従来のコードと変わりありません。
また、strictモードでは、非strictモードで許容されていた曖昧な実装を厳密に処理するようになります。

  • 本来使うべきではない機能を使えなくする
  • エラーにすべきコードをエラーとして処理する

これらのことから、strictモードを一言でまとめると「JavaScriptの正しいコーディングのみを許可するモード」であると言えるでしょう。つまり、

  • strictモード準拠のコードは非strict環境下でも正しく動作する。
  • strictモード非準拠(つまり従来のJavaScriptコード)はstrictモードでの動作保証はされない。

ということになりますね。

言い切ってしまうのはどうかと思いますが、基本的な考え方としてはこれでOKだと思います。

また、strictモードではいくつかの構文や機能が使用できなくなりますが、基本的にこれらは別のアプローチで代替することが出来るはずです。非strictのコードをstrictなコードに置き換える際はこれらの代替手段を用いる必要があります。

strictモードの使い方

strict モードはスクリプト全体または個別の関数に適用できます。以下にそれぞれのケースでの使い方を簡単に説明します。

スクリプト全体への適用

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

"use strict";
var message = "this code is Strict Mode!";

ただ、こちらのやり方には重大な落とし穴がありますので注意が必要です。詳細は次項で説明します。

関数に対しての適用

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

//この関数はstrictモードで動作
function strict() {
    "use strict";

    var message = "this function is Strict Mode!";
    return message;
}

//この関数は非strictモードで動作
function noStrict() {
    var message = "this function is Not Strict Mode!";
    return message;
}

関数単位でstrictモードを適用する場合に意識しておかなければならないことは、“use strict”宣言は、関数スコープにつき1つのみということです。

例えば、次のコード例では関数「inner」は関数「outer」の内側で定義されています。この時、outer 内で既に”use strict”が宣言されている場合は、inner での”use strict”宣言は不要です。
ちなみに、JSLintでこのコードをチェックすると、inner関数内の”use strict”宣言部分で「Unnecessary ‘use strict’」という警告が表示されます。

function outer() {
    "use strict";

    function inner() {
        "use strict";  //ERROR: Unnecessary 'use strict'.

        window.console.log("hoge");
    }

    inner();
}

「スクリプト全体への適用」の落とし穴

上記2つの方法を比べると、一見スクリプト全体にstrictモードを適用した方が”use strict”宣言が1度で済むので良いと思うかもしれません。(私がそうでした) しかし、この方法には注意すべき重大な落とし穴があります。

それは、スクリプトをむやみに連結できない、ということです。

連結する最初のスクリプトの先頭に”use strict”;宣言がある場合、その後に連結された全てのスクリプトは(たとえそれがstrictモード非準拠のコードであっても)全てstrictモードで動作してしまいます。
strictモード非準拠のコードをstrictモードで動作させると思わぬところでエラーが発生する可能性があります。

「スクリプト全体への適用」をする場合、連結するすべてのスクリプトがstrict、または非strictなコードに統一されていれば問題はありません。strictと非strictの混在が問題となります。

JavaScriptでは、プロジェクトの規模が大きくなればなるほど、外部ライブラリやプラグインなどを取り込む(=連結する)ことも多くなると思います。
しかし、それらの外部ファイルがすべてstrictモード準拠のコードであるとは限りません。スクリプト全体へstrictモードを適用し、これらの外部ファイルを連結すると、その結果外部ファイルはstrictモードで動作することになりますが、外部ファイルがstrictモード準拠でなかった場合エラー、もしくは予期せぬ振る舞いをする可能性があります。

こういったことを回避するために、少なくとも現状ではstrictモードは関数単位で適用するようにした方が良いでしょう。

ちなみに、JSLintのデフォルト設定下では、”use strict”文は関数の先頭に記述することが強制されます。これも上記のような理由からではないかと思います。

strictモード対応ブラウザ

各主要ブラウザのstrictモードへの対応状況は以下と通りです。
現時点で、ほぼ全ての主要ブラウザの最新バージョンではstrictモードがサポートされていることが分かります。

ブラウザ strictモード対応バージョン
IE 10.0以上
Firefox 4.0以上
Chrome 13.0以上
Safari 6.0以上
Opera 11.6以上
iOS Safari 5.0以上
Android 3.0以上

使うべきか?(まとめ)

さて、まとめです。

strictモードを一言で説明すると、JavaScriptの次期バージョンに備え、正しいコーディングのみを許可するモードです。
そして、本エントリ作成時点ではほぼすべての主要ブラウザの最新バージョンでstrictモードがサポートされていますので動作検証も容易に行えます。
また、非strictモード環境下(strictモード非対応ブラウザ)においても、strictなコードは正しく動作するといったことを踏まえると、strictモードは積極的に採用すべきだと私は考えます。

ただし、採用するにあたっては以下の点に注意してください。

  • strictモードの適用は、「スクリプト全体」ではなく、「関数単位での適用」とする(←※これ重要!)
  • 可能であれば、strictモード対応ブラウザ、非対応ブラウザの両方でコードの検証を行う

また、非strictな既存の関数を、strict準拠な関数に置き換える場合は、以下の点に注意が必要です。

  • strictモードで禁止されている構文を使用している場合は、代替手段にて対応する必要がある。
  • strictモード準拠な関数に置き換えた後でも、同じ結果が得られているかをしっかりとチェックする。

特に、「非strict→strictへの移行」の場合は、注意(というか、入念な検証)が必要です。

新規でコードを書く場合は、積極的にstrictモードを採用すべきだと思いますが、「非strict→strictへの移行」の場合、例えばstrictモードで禁止されている事項を利用することを前提にしてしまっている場合、コードの大幅な見直しが必要になるかもしれません。

そういった場合、strictモードへの移行にはかなりの時間がかかることもありますので、この辺は状況に応じて判断することになると思います。



最後に余談ですが、書いたコードをJSLintでチェックしている場合は、デフォルト設定であればstrictモード準拠のコードになっていると思うので、日頃からJSLintを使う習慣がついていれば、strictモードの敷居はとても低いものだと思います。そういった意味でもJSLintを使う習慣を身に着けておくことは大切ですね。