まず有効な記述法について。scopeが異なるので以下の例では2重定義とはなりません。
//外側block scope
let a=-~-~0-~-~0;
const b=1e+999;
//内側block scope
{
let a=new class{};
const b=class{};
class c{}
}
などといった芸風はよく見掛けますね。しかし以下のような記述をすると不思議な事が起こります。
eval("let a=0;const b=1;class c{}");
console.log(typeof a, typeof b, typeof c)
evalの中には{}
が無いのでa
、b
、c
はconsole.logと同一scopeに存在しているかのような印象を受けますが、実際にはeval実行部分でしか参照できません。よってconsole.logではundefinedが出力されます。
以下の記述は{}
を省略できる状況でvar
とlet
による変数宣言やclass
定義を試みています。しかしなんとvar
以外は文法違反です(当たり前田)。evalみたいな事ができません。
if(1)var a=0;
if(1)let b=0;
if(1)class c{}
setTimeoutを使うと何やら様子がおかしいですよ…。
{
setTimeout("let a=0",0);
}
setTimeout(_=>console.log(a),1000)
何と変数aは外側のscopeに解き放たれています。結果として0
が出力されます。そして驚くべき事に、block scope内の関数scopeでsetTimeoutを実行しても変数は最も外側のscopeに転がっていきやがります。
{
let f=()=>{setTimeout("let a=0",0)};
f()
}
setTimeout(_=>console.log(a),1000)
当然setIntervalでもその挙動は同じ事になります。以下の例だと即座にIdentifier 'a' has already been declared
などとError警察からの通知が届きます
setInterval("let a=0",500)
以下の例は通常は文法違反となる2重定義。
f=a=>{let a=0}//引数の名前と被ってますよ
しかしsetTimeout様の手に掛かれば見事違反を貫通。
f=a=>{setTimeout("let a=0")}
では問題。以下の処理ではconsole.logで何が出力されるかな? 今までの説明で楽勝でしょう。
f=a=>{
setTimeout("let a=0");
setTimeout(_=>console.log(a),1000)
}
f(1)
ちなみに最外殻scopeの汚染を防ぐには"{...}"
のように処理を囲めばよい。
setTimeout("{let a=0}")
余談にはなりますがsetTimeoutの第一引数に文字列を渡した場合、その中では大域変数と最外殻scopeの変数しか参照できません。block scopeや関数scopeの変数を参照したければ第一引数に関数を渡す事になります。
{
let a=0;
setTimeout(_=>console.log(a))
setTimeout("console.log(a)")//error
}
以上の事からsetTimeout("...")
の文字列中の処理は最外殻scopeに記述しているのと同じ事かもしれません