Caution
お使いのブラウザはJavaScriptが実行できない状態になっております。
当サイトはWebプログラミングの情報サイトの為、
JavaScriptが実行できない環境では正しいコンテンツが提供出来ません。
JavaScriptが実行可能な状態でご閲覧頂くようお願い申し上げます。
JavaScript
応用編
- トップページ
- JavaScript応用編 - strictモード(厳格モード)について
strictモード(厳格モード)について
みなさまどうも。続きまして『strictモード』(厳格モード)について色々とやっていきたいと思います。
『strictモード』とはより厳格でシビアなJavaScriptとしてレンダリングさせるためのモードで、『strictモード』でJavaScriptを実行させるとやってしまいがちなミスや曖昧な実装をエラーとして拾ってくれるようになったり、今後想定されるであろうJavaScriptの基準で構築できたり、処理によっては通常モードよりも少し高速に処理できるようになったりと通常のJavaScriptとは違った感じで動作するようになります。
その反面、色々と制約が増えてしまったり、記述が増えてしまったりとデメリットもかなりあったりしますので『strictモード』を導入するかしないかは好みが別れるところになりますね。著者の体感では企業全体の3%くらいは導入してるんじゃないかなーって感じです。
(´-`).。oO(著者調べなのであまりあてになりません...ご注意を...)
というわけでやっていきましょう。
まず『strictモード』を開始するには『"use strict"』と記述すればOKです。これが『strictモード』を使用する宣言に該当する感じですね。
"use strict"; // 『strictモード』でJavaScriptを実行させます。 // 適当に処理... // ... // ... // ...
この『"use strict"』を記述する際にいくつか注意点があり、まずスコープ毎(グローバルスコープとローカルスコープ)にそれぞれ宣言することが可能である、というところがあります。
グローバルスコープで宣言する場合はこんな感じになります。
"use strict"; // グローバルスコープを対象にした『strictモード』を宣言します。 // 適当に処理... // ... // ... // ...
JavaScriptでのスコープは関数の外と中にしか存在しないため、関数の外にバシッと『"use strict"』を記述すればグローバルスコープが『strictモード』でレンダリングされることになります。
ただし、ちょっと注意点がありJavaScriptを外部ファイルで読み込ませ、そこに『"use strict"』を記述した場合はその外部ファイル内のみ、script要素を使ってインラインで『"use strict"』を記述した場合はそのscript要素内のみが『strictモード』の対象となります。
こんな感じですね。
<script> "use strict"; // 『strictモード』を宣言します。対象となるのはこのscript要素の中だけです。 // このscript要素の中は『strictモード』です。 </script> <script> // 手前のscript要素で『strictモード』の宣言がありますが、このscript要素の中で宣言されているわけではないためこのscript要素の中身は『strictモード』として処理されません。 </script> <script src="./common.js"></script> <!-- このファイルを『strictモード』で実行させたい場合はこのファイルの中で『strictモード』を宣言する必要があります。 --> <script src="./test.js"></script> <!-- このファイルを『strictモード』で実行させたい場合はこのファイルの中で『strictモード』を宣言する必要があります。 -->
これについては、外部のJSファイルもインラインのJSもscript要素を使って記述しますので「script要素ごとに『strictモード』の宣言をする必要がある」と覚えちゃうのが良いかもしれません。これ、結構勘違いしやすいところなんでご注意ください。
続いてローカルスコープで宣言する場合をみていきましょう。
関数(メソッド)の中に『"use strict"』を記述すればローカルスコープ、つまりその関数の中だけ『strictモード』でレンダリングされます。
(function(){ "use strict"; // この関数(ローカルスコープ)を対象にした『strictモード』を宣言します。 })(); var f = function(){ // ここは『strictモード』の対象外です。 };
関数(メソッド)が入れ子状態になっている場合では外側の関数で『strictモード』の宣言がされていれば内側の関数も『strictモード』が適用されます。
(function(){ "use strict"; // この関数(ローカルスコープ)を対象にした『strictモード』を宣言します。 var f = function(){ // 内側の関数なので、ここも『strictモード』の対象になります。 }; })();
ローカルスコープで『strictモード』を宣言する場合についてはこんな感じです。
続いてグローバルスコープとローカルスコープの両方に存在する注意点として、『strictモード』の宣言は、適用させたいスコープの先頭あたり(他の文の前)に記述する必要があったりします。
以下のサンプルを見てみましょう。
"use strict"; // グローバルスコープを対象にした『strictモード』で実行させます。 // 適当に処理... // ... // ... // ...
この場合は記述されたscript要素全体、または外部のJSファイル全体が『strictモード』になりますが以下の場合はダメです。
console.log(this); "use strict"; // 手前に別の処理(文)が記述されてしまっているので『strictモード』の宣言として認識されません。 // 適当に処理... // ... // ... // ...
『console.log(this);』という記述が宣言の前に書かれているためNGになります。他の処理(文)よりも前に『"use strict"』と記述しなくてはいけない、という感じですね。これはローカルスコープでも同じです。
(function(){ console.log(this); "use strict"; // 手前に別の処理(文)が記述されてしまっているので『strictモード』の宣言として認識されません。 })();
こちらも気をつけて下さい。
『"use strict"』の前に文字列リテラルは記述してOKだったりします。
"test"; // 『"use strict"』の前に文字列リテラルを記述します。 "use strict"; // ちゃんと動きます。 // 適当に処理... // ... // ... // ...
これは多分、将来に文字列リテラルを使用した他の宣言などが入るかもしれないという可能性を考えての仕様じゃないかと思います。違ったらすみません。
さて、続きまして『strictモード』を実用する際の注意点を確認していきましょう。
最近のウェブサイト構築では通信負荷とファイル容量を下げつつレンダリング速度を向上させる目的で複数のJavaScriptを結合してさらにそれを圧縮してから公開するといった手法が取られています。
このファイル結合をした時に『strictモード』の宣言があるとちょっと困ったことになってしまう場合があります。
例えば以下の2つのJSファイルが存在するとして、
"use strict"; // このJavaScriptファイルは『strictモード』に則って構築されているものとします。 // 適当に処理... // ... // ... // ...
(function(){ // このJavaScriptファイルは『strictモード』に則って構築されていないものとします。 // 適当に処理... // ... // ... // ... })();
これが結合されるとこうなります。
"use strict"; // このJavaScriptファイルは『strictモード』に則って構築されているものとします。 // 適当に処理... // ... // ... // ... (function(){ // このJavaScriptファイルは『strictモード』に則って構築されていないものとします。 // 適当に処理... // ... // ... // ... })();
もうお分かりになったかと思いますが、最初のJavaScriptファイルの冒頭で『strictモード』の宣言があるため、結合されたファイル全体が『strictモード』として実行されてしまいます。
ネット上や参考書などで公開されている全てのJavaScriptのソースコードとライブラリは『strictモード』の基準に則ってコーディングされているわけではありません。JavaScriptファイルを圧縮したあとに公開する構造になっているウェブサイトで『strictモード』を使用する場合は要注意ですね。
これの対策として、ローカルスコープのみで『strictモード』の宣言を行う、という手法が取られることが多いです。こんな感じですね。
var f = function(){ "use strict"; // 関数オブジェクトの冒頭で必ず『strictモード』の宣言を入れるようにします。 }; var _f = function(){ "use strict"; // 関数オブジェクトの冒頭で必ず『strictモード』の宣言を入れるようにします。 }; (function(){ "use strict"; // 関数オブジェクトの冒頭で必ず『strictモード』の宣言を入れるようにします。 })();
script要素全体やファイル全体に対して一気に『strictモード』の宣言を行いたい場合は全体を即時関数などでラップして、そこの冒頭で宣言をすると良いかもです。
(function(){ // 全体を即時関数でラップします。 "use strict"; // ここで『strictモード』の宣言をします。 var f = function(){ // ここは『strictモード』になります。 }; var _f = function(){ // ここは『strictモード』になります。 }; })();
こんな感じで『strictモード』の宣言はローカルスコープのみ、という構築にすれば『strictモード』の基準に則ってコーディングされてないソースコードを含めて結合する場合も安心です。
ちょっと手間がかかりますが、こればっかりはしょうがないので諦めて対応しちゃいましょう。
では続いて通常モードと『strictモード』の違いについて確認していきます。通常モードと『strictモード』の違いは以下です。
- 『strictモード』ではwith文は使用できません。
- 『strictモード』では全ての変数をちゃんと宣言しないとエラーがでます。
- 『strictモード』ではグローバル関数とメソッド以外の関数全ての『this』の参照先がwindowオブジェクトではなく『undefined』となります。
- 『strictモード』では『call()』と『apply()』で呼び出された関数オブジェクトの『this』の参照先が、『call()』と『apply()』の第1引数に渡されたものと全く同じになります。
- 『strictモード』では書き込み不可だったり拡張不可だったりするオブジェクトとか値とかを上書きするとエラーがでます。
- 『strictモード』では『argumentsオブジェクト』の上書きは出来ません。
- 『strictモード』では『arguments.callee』の使用はできません。
- 『strictモード』では『eval』と『arguments』は予約語となるので識別子としての使用はできません。
- 『strictモード』では同じ識別子で複数のプロパティを定義しようとするとエラーがでます。
- 『strictモード』では関数オブジェクト定義の際の仮引数に複数の同じ識別子を使用するとエラーがでます。
- 『strictモード』では8進数は使えません。
- 『strictモード』では通常モードで可能だった『eval()』の呼び出し側のスコープの変数定義と関数定義が出来ません。
こんな感じで結構多いです。ちょっと大変かもしれませんがひとつひとつ確認していきましょう。
まずwith文が使用できない、ということなんですがこれはそのままの意味になります。with文を使用しようとすると怒られちゃいます。
(function(){ // 全体を即時関数でラップします。 "use strict"; // 『strictモード』の宣言をします。 with(Math){ // エラーでちゃいます。 } })();
JavaScriptには渡したオブジェクトをスコープチェーンの最初に割り込みさせることができるwith文という構文が用意されています。こんな感じの構文になりますね。
with(Math){ floor(3.14); // 数値『3』が出力されます。 }
with文の中ではwith文の『()』の中に指定されたオブジェクトが優先的に読み込まれる、というわけなので上記のサンプルのようにwith文の『()』の中に『Math』を渡してしまえば、『floor()』と記述するだけで小数点以下を切り捨てできる『Math.floor()』を呼び出すことができたりちゃいます。ソースコードがすっきりするし、ファイル容量の節約に成功できていますね。
ただ、このwith文は処理が遅いのと、以下のようにwith文の中で定義していない変数などを参照しようとした場合にwith文の『()』のプロパティが優先的に読み込まれるため、ちょっと想像と違った結果になってしまったりする場合があります。
var floor = true; with(Math){ console.log(floor); // グローバル変数『floor』ではなく『Math.floor』が呼び出されます。 }
なのでwith文は使用されることは少なめです。『strictモード』では動きませんし、最近では非推薦という形になっちゃってますね。
もし何かのオブジェクトを繰り返し呼び出す場合などで記述量を削減したい、といった場合には変数とかオブジェクトとかを用意して、そこにそのオブジェクトを突っ込んじゃいましょう。以下の2つのソースコードは同じ結果となります。
with(Math){ floor(3.14); ceil(3.14); round(3.14); }
var m = Math; // Mathオブジェクトをオブジェクト『m』で参照できるようにします。 m.floor(3.14); // 記述を短くできます。 m.ceil(3.14); m.round(3.14);
続いて『strictモード』では変数とかの宣言が必ず必要になる、といったところを確認していきましょう。これは『var』を記述せずに変数とかに値を突っ込んだりした場合にエラーを出してくれるって挙動になります。
通常モードでは『var』を付けずに(スコープで定義されていない)変数とかに代入するとグローバル変数、またはグローバルオブジェクトが作成されます。
(function(){ x = 0; // 定義されていない変数に代入するとグローバル変数『x』として定義されます。 })();
『strictモード』で定義されていない変数とかに代入とかしようとするとさくっとエラーとなります。
"use strict"; // 『strictモード』の宣言をします。 x = 0; // エラーです。
(function(){ "use strict"; // 『strictモード』の宣言をします。 x = 0; // エラーです。 })();
『var』の書き忘れで意図せずグローバル汚染をしてしまうミスは結構やりがちなのでこれはかなり強力です。
『strictモード』でローカルスコープの中(関数オブジェクトの中)からグローバル変数やグローバルオブジェクトを定義したい場合は『window.』を付けて定義して下さい。
(function(){ "use strict"; // 『strictモード』の宣言をします。 window.x = 0; // JavaScriptのグローバルの実態は『windowオブジェクト』なので『window.x』とすることでグローバル変数として定義できます。 })();
※windowオブジェクトについての詳しい解説はこちらへ。
続いてグローバル関数とメソッド以外の関数全ての『this』の参照先がwindowオブジェクトではなく『undefined』となる、というところを見ていきましょう。
通常モードではグローバル関数とメソッド以外の全ての関数の『this』の参照先はwindowオブジェクトになります。
var f = function(){ console.log(this); // 『windowオブジェクト』が出力されます。 var _f = function(){ console.log(this); // 『windowオブジェクト』が出力されます。 }; _f(); };
これを『strictモード』で実行させた場合は『undefined』となります。
"use strict"; // 『strictモード』の宣言をします。 var f = function(){ console.log(this); // 『undefined』が出力されます。 var _f = function(){ console.log(this); // 『undefined』が出力されます。 }; _f(); };
new演算子を忘れてしまった場合に意図せずグローバル汚染をしてしまう、というミスを防げるのでこの機能は中々便利です。
そして、この『this』の参照先が変わる挙動を利用すると、そのブラウザが『strictモード』に対応しているかどうかの判定ができます。以下のような感じですね。
var hasStrictMode = function(){ "use strict"; // 『strictモード』の宣言をします。 return this === undefined; // 『strictモード』で実行できているならば『this』の値が『undefined』となるため、『this === undefined』とすることで『strictモード』を搭載しているかどうかの判定ができます。 }();
(´-`).。oO(上記のサンプルはO'Reillyさんの『JavaScript第6版』を参考にさせて頂きました...)
(´-`).。oO(いつも大変お世話になっております...)
(´-`).。oO(一冊手元にあるととっても便利なので皆様もぜひ...)
(´-`).。oO(ちなみにこの本は百科事典レベルの厚みがあるので買うとしたら電子書籍版がオススメです...)
続いて『call()』と『apply()』の動作の違いについてです。通常モードで『call()』と『apply()』の第1引数にプリミティブ値を渡すと勝手に変換されちゃったりします。
var f = function(){ console.log(this); }; f.call(1); // 第1引数に数値を渡すと『Numberオブジェクト』が渡されたものとして処理されます。 f.call("abc"); // 第1引数に文字列を渡すと『Stringオブジェクト』が渡されたものとして処理されます。 f.call(true); // 第1引数に真偽値を渡すと『Booleanオブジェクト』が渡されたものとして処理されます。 f.call(null); // 第1引数に『null』を渡すと『windowオブジェクト』が渡されたものとして処理されます。 f.call(undefined); // 第1引数に『undefined』を渡すと『windowオブジェクト』が渡されたものとして処理されます。
『strictモード』では変換されることはありません。
"use strict"; // 『strictモード』の宣言をします。 var f = function(){ console.log(this); }; f.call(1); // 数値『1』が出力されます。 f.call("abc"); // 文字列『abc』が出力されます。 f.call(true); // 『true』が出力されます。 f.call(null); // 『null』が出力されます。 f.call(undefined); // 『undefined』が出力されます。
※『call()』と『apply()』についてはこちらへどうぞ。
続いてJavaScriptでは、普通なら書き込み禁止となっているはずであろう元々用意された値を上書きできてしまうという謎仕様なんですが、
undefined = true; // 『undefined』を上書きできちゃいます。
『strictモード』ならちゃんとエラーを出力してくれるようになります。
"use strict"; // 『strictモード』の宣言をします。 undefined = true; // エラーがでます。
そんでもって、通常モードでは『argumentsオブジェクト』を上書きすると仮引数も上書きされてしまったりするのですが、
var f = function(x){ console.log(x); // 第1引数を出力します。 arguments[0] = "ワンパンマン"; // arguments[0]を上書きします。これは仮引数『x』を上書きすることと同義、つまり『x = "ワンパンマン"』と記述した場合と同じになります。 console.log(x); // 第1引数が上書きされて文字列『ワンパンマン』が出力されます。 console.log(arguments[0]); // 文字列『ワンパンマン』が出力されます。 }; f(0); // 数値『0』が出力されたあとに文字列『ワンパンマン』が2回出力されます。
『strictモード』の『argumentsオブジェクト』はその関数オブジェクトに渡された仮引数とは切り離されたものとして扱われます。
"use strict"; // 『strictモード』の宣言をします。 var f = function(x){ console.log(x); // 第1引数を出力します。 arguments[0] = "ワンパンマン"; // 『arguments[0]』を上書きしていますが、『strictモード』では『arguments[0]』と仮引数『x』は別のものとして扱われるため、仮引数『x』が上書きされることはありません。 console.log(x); // 第1引数を出力します。 console.log(arguments[0]); // 文字列『ワンパンマン』を出力します。 }; f(0); // 数値『0』が2回出力された後に文字列『ワンパンマン』が出力されます。
さらに『arguments』と『eval』は予約語になるので識別子(変数とかの名前)として使用することができなくなり、自身の関数オブジェクトを参照できる『arguments.callee』の使用も不可となります。
"use strict"; // 『strictモード』の宣言をします。 (function(){ var arguments = true; // エラーが出ます。 var eval = true; // エラーが出ます。 console.log(arguments.callee); // エラーがでます。 })();
※『argumentsオブジェクト』についてはこちらへどうぞ。
続いて定義の際に同じ識別子は使えない、という挙動についてです。
通常モードではプロパティとかを定義する時や、仮引数に同じ識別子を使用したところでより後に記述されるほうが優先されるだけで特にエラーが出たりしないわけでございますが、
var x = { // 『x.a』は数値『1』になります。 a: 0, a: 1 }; var f = function(x, x){ console.log(x); // 第2引数が出力されます。 };
『strictモード』の場合はエラーが出ます。
"use strict"; // 『strictモード』の宣言をします。 var x = { // エラーです。 a: 0, a: 1 }; var f = function(x, x){ // エラーです。 console.log(x); };
ちょっと勘違いされやすいところですが、プロパティの定義の際に同じ識別子を使用できない、というだけで再代入という形ならば問題はありません。
"use strict"; // 『strictモード』の宣言をします。 var x = { a: 0 }; x.a = 1; // このような再代入の形なら問題ありません。
続いて8進数は使えない、というところですがこれは特に問題ないかと思います。
JavaScriptでは言語の特性上、10進数だけあれば問題ない事が多く、他の言語でも使用されるのは2進数と10進数と16進数で、8進数を使用することは稀です。
では最後に『eval()』の挙動を確認していきましょう。
『eval()』は渡された文字列をそれをJavaScriptの演算式とみなして演算してくれるというちょっと不思議な処理をしてくれる関数になります。
var x = eval("1 + 2"); console.log(x); // 数値3が出力されます。
通常モードの『eval()』はちょっと問題があり、以下のように『eval()』の引数の中で変数とかを定義するとその変数はグローバルとして定義されてしまいます。
eval("var x = true;"); console.log(x); // グローバル変数『x』が作成されてしまうため、『true』が出力されます。
『strictモード』の『eval()』ならばちゃんとローカルスコープとして変数とかを定義してくれます。
"use strict"; // 『strictモード』の宣言をします。 eval("var x = true; console.log(x);"); // 変数『x』を定義します。『strictモード』ならばeval()の内部からのみ変数『x』を参照できます。 console.log(x); // グローバル変数『x』は作成されていないためちゃんとエラーになります。
『strictモード』を開始する『"use strict"』という宣言を『strictモード』が搭載されていないブラウザで実行した場合はただの文字列リテラルが記述されたという扱いになります。
"use strict"; // 『strictモード』が対応していないブラウザではただの文字列リテラルが記述されたという動作になります。 // 適当に処理... // ... // ... // ...
なので基本的に『strictモード』は下位互換があることになりますので気兼ねなく使っちゃってOKです。
ただし、ソースコードに『eval()』が含まれる場合は先程の解説通りグローバル汚染が行われてしまう可能性があるため結果が変わってしまう場合があります。
以下は同じソースコードで結果が変わってしまった一例です。
var x = 0; // グローバル変数『x』を定義します。 // 適当に処理。 // ... // ... // ... eval("var x = true"); console.log(x); // グローバル変数『x』は上書きされてしまったため『true』が出力されます。
"use strict"; // 『strictモード』の宣言をします。 var x = 0; // グローバル変数『x』を定義します。 // 適当に処理。 // ... // ... // ... eval("var x = true"); console.log(x); // 数値『0』が出力されます。
『eval()』が使用されている場合はしっかりチェックをするようお気をつけ下さい。
というわけで長くなりましたが以上になります。
『strictモード』を導入するかどうかは好みになりますので、皆様の考えのもと決定して頂ければ幸いでございます。
ではこの辺で。またお会いしましょう。
この記事は桜舞が執筆致しました。
著者が愛する小型哺乳類 |
桜舞 春人 Sakurama HarutoISDN時代から様々なコンテンツを制作しているちょっと髪の毛が心配な東京在住のプログラマー。生粋のロングスリーパーで、10時間以上睡眠を取らないと基本的に体調が悪い。好きなだけ寝れる生活を送るのが夢。ゲームとスポーツと音楽が大好き。誰か髪の毛を分けて下さい。 |
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。