◆ 通常モードと strict モードでの変更点まとめ

class はともかくとして module を使うと強制 strict モードになるので どう変化するのかを調べてみました
すでに知ってるものや ECMAScript の spec (現時点で 2017) を流し見してまとめたものなので すべてを網羅できているかはわかりません


確認は Chrome と Firefox で確認して同じ動きになってました
エラーメッセージについてはブラウザごとに違います

arguments.callee

引数一覧にアクセスするための arguments オブジェクトのプロパティに自身の関数を参照する callee プロパティがあります
strict モードではこれが使えなくなります

function foo(){
"use strict"
console.log(arguments.callee)
}
foo()
// 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them

通常はその関数自身の参照が入っています
function foo(){
console.log(arguments.callee)
}
foo()
// ƒ foo(){
// console.log(arguments.callee)
// }


再帰関数を使う場合には 便利なので使えたほうがいいのですけどね
自身の関数名を書く方法ではコピペして使う場合に毎回関数名を変える必要があります
それを避けるために
function fn(){
return function recur(){
if(0){
recur()
}
}()
}
こういう風に あえて共通になるようローカルスコープに関数を作って実行させるなどしているのですが 一手間増えています


arguments オブジェクトには caller プロパティもありました
ES2017 のスペックによると「ES2017 より前のスペックには strict モードだとこのプロパティでもエラーになるように指定されていたけど このプロパティの機能はもう仕様に含んでないので strict モードでエラーになるというルールもなくした」ようです

単純にいうと Chrome/Firefox/Edge だとこのコードは有効です
function foo(){
"use strict"
console.log(arguments.caller)
}
foo()
// undefined
IE はエラーです

func.caller, arguments

arguments.callee と似たもので 関数の caller と arguments プロパティも strict モードでは禁止されていてアクセスするとエラーです

function callFoo(){
foo()
}

function foo(){
"use strict"
console.log(foo.caller)
}
callFoo()
// 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them

function foo(){
"use strict"
console.log(foo.arguments)
}
callFoo()
// 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them

非 strict モードでは自分を呼び出した関数と arguments オブジェクトを取得できます
function callFoo(){
foo()
}

function foo(){
console.log(foo.caller)
console.log(foo.arguments)
}
callFoo()
// ƒ callFoo(){
// foo()
// }
// Arguments [callee: ƒ, Symbol(Symbol.iterator): ƒ]


私は全く使ったこと無いのでこれは消えても困らない機能です
一見便利そうですが自分を呼び出した関数を知れるってなんか変な感じがしますし

一応 strict モードでも関数名だけならエラーの stack から取れます
function callFoo(){
"use strict"
foo()
}
function foo(){
"use strict"
const stack = new Error().stack
const call_stack = stack.split("\n").slice(1)
.map(e => e.replace(/^.*?at (.*?) .*$/, "$1"))
.filter(e => !e.startsWith(" "))
console.log(call_stack)
}
callFoo()
// ["foo", "callFoo"]

取れはしますがフォーマットはブラウザ依存ですし バージョンアップで変わってることもあるのでやらないほうがいいと思います
上のコードは Chrome では動きますが Firefox だと異なる結果になります

Reserved words

strict モードでは予約語がちょっと多いです

function foo(){
"use strict"
let public = true
return public
}
foo()
// Unexpected strict mode reserved word
function foo(){
let public = true
return public
}
foo()
// true

strict モードではエラーになるのを調べます

const keywords = ["arguments", "await", "break", "case", "catch", "class", "const", "continue", "debugger", "default",
"delete", "do", "else", "eval", "export", "extends", "finally", "for", "function", "if", "import", "in",
"instanceof", "let", "new", "return", "static", "super", "switch", "this", "throw", "try", "typeof",
"var", "void", "while", "with", "yield", "implements", "package", "protected", "interface", "private", "public"]
for(const keyword of keywords){
if(strict(keyword) ^ nonStrict(keyword)){
console.log(keyword)
}

function strict(name){
"use strict"
try{
eval("var " + name)
return true
}catch(err){
return false
}
}
function nonStrict(name){
try{
eval("var " + name)
return true
}catch(err){
return false
}
}
}
arguments
eval
let
static
yield
implements
package
protected
interface
private
public

arguments と eval は変数(関数)なのですが代入や宣言はできません
引数名や catch で受け取る変数名としても使えません

non strict モードでも let や const の中に let という名前は使えないなど特殊な制限もあります
var だと大丈夫です
let let = 1
const let = 1
// let is disallowed as a lexically bound name

Octal literals

8 進数は 0 から始めることで書くことが出来ますが strict モードでは禁止されています

function foo(){
"use strict"
console.log(010)
}
foo()
// Octal literals are not allowed in strict mode.
function foo(){
console.log(010)
}
foo()
// 8

文字列中のエスケープシーケンスで \ のあとに数字を書くと 8 進数の文字コードとして文字になります
数値リテラルと違って最初の 0 はなくてもいいです
逆に 3 桁の場合に 0 をつけて 4 桁にすると正しく入力できなくなります
これも strict モードでは禁止されています
function foo(){
"use strict"
console.log("\041")
}
foo()
// Octal escape sequences are not allowed in strict mode.
function foo(){
console.log("\041")
console.log("\101")
}
foo()
// !
// A


代わりに o を使って 0o12 のようにする 8 進数リテラルは使えます
0x や 0b にあわせる形です
ただしエスケープシーケンスの方はできません
16 進数の \u や \x を使うことになります
function foo(){
"use strict"
console.log(0o10)
console.log("\0o41")
console.log("\x41")
}
foo()
// 8
// o41
// A


Invalid assignment

getter しかなかったり 変更不可にされているプロパティに書き込むと strict モードではエラーになります

function foo(){
"use strict"
const obj = {get bar(){return 1}}
obj.bar = 1
}
foo()
// Cannot set property bar of #<Object> which has only a getter

通常は変更されず何も起きません
function foo(){
const obj = {get bar(){return 1}}
obj.bar = 1
}
foo()
// no error

Invalid delete

delete 演算子で削除できない変数やプロパティを消そうとすると strict モードではエラーになります
function foo(){
"use strict"
const value = 1
delete value
}
foo()
// Delete of an unqualified identifier in strict mode.

function foo(){
"use strict"
const obj = Object.create({}, {prop: {writable: false, value: 1}})
delete obj.prop
}
foo()
// Cannot delete property 'prop' of #<Object>
function foo(){
const value = 1
delete value
console.log(value)
const obj = Object.create({}, {prop: {writable: false, value: 1}})
delete obj.prop
console.log(obj)
}
foo()
// 1
// {prop: 1}


消せなくてもこういうエラーにならないケースもあります
function foo(){
"use strict"
let x = 1
delete x++
}
foo()

function foo(){
"use strict"
delete 1
}
foo()

function foo(){
"use strict"
delete {}
}
foo()

Duplicate parameter name

引数の名前が重複すると strict モードではエラーです

function foo(a, a){
"use strict"
return a
}
foo(1, 2)
// Duplicate parameter name not allowed in this context

function foo(a, a){
return a
}
foo(1, 2)
// 2

これがあるといらない引数を同じ名前で受け取ってしまえないので不便です
こういう使い方ができません
text.replace(regexp, (_, _, flag, body) => { /* */ })
_1, _2, .... という感じで連番にするか いったん全部受け取って中で展開ということもできます
text.replace(regexp, (...a) => {
const [,, flag, body] = a
/* */
})
複数行あるならこれでもいいですけど 一行で書ける場合はあまり使いたくない方法です

Duplicate property name

オブジェクトリテラルの中に同じ名前を書いた場合です
昔はダメだったのですが今では許可されています
引数もこうなってくれればいいのですけどね

function foo(){
"use strict"
const obj = {a:1, a:2}
return obj.a
}
foo()
// 2

IE では
strict モードでは、プロパティの複数定義は許可されません。
とエラーになります


許可された理由はたぶん ... を使ってこういうことができるようになったから
const subobj = {a:10, b: 20}
const obj = {a:1, ...subobj, b:2}

重複した場合は後にあるものが優先されます

this

strict モードでの this のデフォルトはグローバルオブジェクトでなく undefined です

function foo(){
"use strict"
return function(){return this}()
}
foo()
// undefined

非 strict モードではグローバルオブジェクトを参照できます
function foo(){
return function(){return this}()
}
foo()
// Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}

また strict モードでは this がオブジェクトとは限りません
通常の動作では bind 等で null を this に設定すると自動でグローバルオブジェクトに置き換えられましたが strict モードではそのままの値です

function foo(){
"use strict"
return function(){return this}.bind(null)()
}
foo()
// null
function foo(){
return function(){return this}.bind(null)()
}
foo()
// Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}

プリミティブ値もオブジェクト化されずそのままです

function foo(){
"use strict"
return function(){return this}.bind(1)()
}
foo()
// 1
function foo(){
return function(){return this}.bind(1)()
}
foo()
// Number {1}

prototype 拡張してメソッドをつくるときの this もそのままの値です

String.prototype.foo = function(){
"use strict"
console.log(this)
}
"text".foo()
// text
String.prototype.foo = function(){
console.log(this)
}
"text".foo()
// String {"text"}

with

変数参照にオブジェクトを割り込ませる with 機能は strict モードでは使えません

function foo(){
"use strict"
with({a:1}){
return a
}
}
foo()
// Strict mode code may not include a with statement
function foo(){
with({a:1}){
return a
}
}
foo()
// 1

Labelled FunctionDeclaration

関数宣言にラベルを使うことができません
関数宣言のみで式には使えます

function foo(){
"use strict"
label: function f(){}
}
foo()
// In strict mode code, functions can only be declared at top level or inside a block.
function foo(){
label: function f(){}
}
foo()

非 strict モードでの動作として仕様的許可されたのは ES2015 の spec かららしいです
ただ実際のところそれ以前でも動いていたようです

そもそもラベル機能自体を使う機会がないので影響受けることはなさそうです

Block-scope FunctionDeclaration

strict モードでは関数宣言のスコープはブロックです

function foo(){
"use strict"
{
function f(){ return 1 }
}
return f()
}
foo()
// f is not a function
function foo(){
{
function f(){ return 1 }
}
return f()
}
foo()
// 1

If FunctionDeclaration

if がある場合の関数宣言は実際に通った部分のみ行われます
スコープは同じく strict だとブロックスコープです

function foo(){
"use strict"
if(true){
function t(){console.log("t")}
}else{
function f(){console.log("f")}
}
typeof t === "function" && t()
typeof f === "function" && f()
}
foo()
// no output
function foo(){
if(true){
function t(){console.log("t")}
}else{
function f(){console.log("f")}
}
typeof t === "function" && t()
typeof f === "function" && f()
}
foo()
// t

for-in の初期値

for-in 文でも変数に初期値を設定することは一応可能です
意味ないですけど
strict モードだとこれが禁止になっています

function foo(){
"use strict"
const obj = {a: 1, b: 2, c: 3}
for (var key = 0 in obj) {
console.log(key)
}
}
foo()
// for-in loop variable declaration may not have an initializer.
function foo(){
const obj = {a: 1, b: 2, c: 3}
for (var key = 0 in obj) {
console.log(key)
}
}
foo()
// a
// b
// c


通常でも使えるのは var による宣言時のみです
let ではエラーになります
let による宣言ができるようになったほうが後なので禁止しても問題は起きないのですが var だと互換性を壊さないためにそのままにしてこんなことになってるようです

function foo(){
const obj = {a: 1, b: 2, c: 3}
for (let key = 0 in obj) {
console.log(key)
}
}
// for-in loop variable declaration may not have an initializer.

eval

予約語のところで arguments と eval は宣言したり代入できない特殊なものになってると書きましたが 動きの面でも違いがあります
strict モードでは eval 内で宣言した変数は eval を実行したスコープと別のスコープに宣言されます
Function コンストラクタで関数を作って実行するタイプの eval に近いです

function foo(){
"use strict"
eval("var bar = 10")
console.log(bar)
}
foo()
// bar is not defined
function foo(){
eval("var bar = 10")
console.log(bar)
}
foo()
// 10

変数宣言する処理を動的に作れないので稀に不便でもあります

var x = 10
function foo(){
var y = x
eval("var x")
x = y + 1
console.log(x)
}
foo()
// 11
console.log(x)
// 10

var が関数の最初で宣言される巻き上げの影響を受けずに 外側の値を参照できて かつ同じ変数名を宣言できたりするので禁止にしたいというのもわからなくはないです

ところで let/const で作るなら通常の動作でも strict モードと同じです

function foo(){
"use strict"
eval("let bar = 10")
console.log(bar)
}
foo()
// bar is not defined
function foo(){
eval("let bar = 10")
console.log(bar)
}
foo()
// bar is not defined

TailCallOptimization

仕様的には TCO は strict モード内でのみ使えるとされています
ただそもそも TCO が Safari でしか実装されていないので今の所気にすることはなさそうです

https://stackoverflow.com/questions/42788139/es6-tail-recursion-optimisation-stack-overflow
https://stackoverflow.com/questions/37224520/are-functions-in-javascript-tail-call-optimized

Global assignment

通常は 宣言していない変数名に代入しようとすると global オブジェクトへ代入されます
strict モードではこれが禁止されています
意図しないグローバル変数を作ってしまっているのはわりと見かける光景ですしこれは良い部分だと思います

function foo(){
"use strict"
globalvar = 1
}
foo()
// globalvar is not defined
function foo(){
globalvar = 1
console.log(globalvar)
}
foo()
// 1

グローバル変数を作れないわけではなく明示的に window のプロパティに代入すれば可能です
function foo(){
"use strict"
window.globalvar = 1
console.log(globalvar)
}
foo()
// 1

Independent arguments and parameter

通常は 引数と引数をまとめたものである arguments オブジェクトは共有された値となっています
プロパティでなくその値自体を書き換えても もう一方が書き換わります
ですが stric モードでは別々のものとなり 書き換えの影響を受けません

function foo(a, b){
"use strict"
arguments[0] = 100
console.log(a)
b = 200
console.log(arguments[1])
}
foo(10, 20)
// 10
// 20

function foo(a, b){
arguments[0] = 100
console.log(a)
b = 200
console.log(arguments[1])
}
foo(10, 20)
// 100
// 200


Primitive property

strict モードではプリミティブ値へのプロパティの追加は禁止されています

function foo(){
"use strict"
const num = 100
num.prop = 200
return num.prop
}
foo()
// Cannot create property 'prop' on number '100'

toString みたいに参照はできるものでも実体はプロトタイプにあるもので プリミティブ値への書き込みはできないのでエラーです

通常の動作では実行自体は可能ですがオブジェクトではないので値は保持されず無意味です

function foo(){
const num = 100
num.prop = 200
return num.prop
}
foo()
// undefined

strict が使えない場合

関数に strict モードが使えないケースがあります

  • default parameter (a = 1)
  • rest parameter (...a)
  • destructuring parameter ({a, b})

function foo(a, b = 1) {
"use strict"
}
// Illegal 'use strict' directive in function with non-simple parameter list

function foo(...args) {
"use strict"
}
// Illegal 'use strict' directive in function with non-simple parameter list

function foo([a, b]) {
"use strict"
}
// Illegal 'use strict' directive in function with non-simple parameter list

強制 strict モード

クラス構文での定義と module としてロードされたスクリプト内は強制的に strict モードにされます

class Foo {
constructor(x, x){ }
}
// Duplicate parameter name not allowed in this context
<script type="module">
function foo(x, x){ }
</script>
<!-- Duplicate parameter name not allowed in this context -->

strict モードを使うかどうか

全体的はほぼ誰も使ってないような機能が多いと思います

私としては困るのは let が予約語になることと 引数名の重複禁止です
const で let という変数を作れませんが 関数なら作れますしたまに作ってます
引数名については上で書いたとおりです

他には eval のスコープと this の動きの違いはあんまり好みじゃないですが困るというほどではないです
Node.js や MongoDB など新しい JavaScript 実行環境に触れたときに グローバルの名前がわからないときはとりあえず this 参照でグローバルオブジェクトを取得していたのですが将来全部 strict モードになったりしたらどうやってアクセスすればいいのでしょうね

あと 書き換え不可プロパティへ代入しても何も起きないことを利用して いちいちチェックしないで書き換えるようにしている作りは少なからずあります
C# や PHP での面倒な null チェックをなくすための ?. とか ?? とか また jQuery でのセレクタに当てはまるのがなければ何もしないという動作も近いものがあると思います
できないなら何もしない を自動でやってくれるのは使う側からしては便利ですからね
読む側はけっこう大変ですけど

それに JavaScript や HTML/CSS などウェブのフロントエンドって基本エラー出さない系だと思います
おかしくても動く部分だけでとりあえず動かすという感じ
個人的にはそういうのが好きで 作ってる途中でとりあえず動くところまで動かさせてほしいです
コンパイルいる言語だとすべてが完璧でないと実行すらできないのでそういう面が苦手です

これまでだと 普段はゆるくてきつめにチェックしたいときにはスクリプトの一行目に全部 use strict つけて strict モードで確認できるという必要なら使うというくらいで使えました
でも module などでは強制となってしまうのでどちらかと言えば嫌な仕様です

強制されないときにあえて使う必要があるかというと 最近なら静的解析とかでも十分チェックしてくれるので 無効な処理のチェックの用途ならわざわざ全部に use strict 書いてまでしなくてもよいと思います
どちらかというと this のような動作の違いをみて好みの方を選べば良いと思います