[トップを置く]JavaScript語法のスコープ(あなたが知らないJavaScript)


JavaScriptは伝統的なブロックレベルのスコープではなく、関数のスコープです.
一、スコープ
1.JavaScriptエンジンはコード実行前にコンパイルされます.この過程で、var a=2のような声明は2つの独立ステップに分解されます.第一歩(コンパイル段階):var aはその作用領域で新しい変数を宣言します.これは最初の段階、つまりコード実行前に行われます.第二ステップ(運転フェーズ):a=2は変数a(LHSクエリ)を照会し、値を割り当てます.2.LHS&RHS(現在のスコープ->上位スコープ->->グローバルスコープ)LHS(左側):変数を探しているコンテナ自体RHS(右側):ある変数の値の例を探します.
function foo(a){
	var b = a;
	return a + b;
}
var c = foo(2);
// LHS(3 ):c;a(      );b;
// RHS(4 ):foo(2);=a;a;b;
3.異常
function foo(a){
	console.log(a + b);
	b = a;
}
foo(2);
(1)ES 5の「厳格モード」において、LHSはReferenceErrを投げ出す;「非厳格モード」では、LHSは自動的に暗黙的にグローバル変数を作成します.(2)RHSクエリは、すべてのネストされた作用領域において、必要な遍歴が見つからない場合、ReferenceErrerをスローします.(3)RHSは変数を調べますが、不当な操作(nullまたはundefinedタイプの属性を参照)を試みたら、TypeErrorを投げます.一言で要約すると、ReferenceErrerは同じスコープでの判別に失敗しました.Type Errerは作用域を代表して判別に成功しましたが、結果に対する操作は不法または不合理です.
PS:ブログの「JavaScript関数とprototype」関数の上書きなどの問題を原理的に述べました.
二、語法のスコープ
語法のスコープは、コードを書く際の関数宣言の位置によって、スコープが決定されることを意味します.JavaScriptには二つの機構があります.「騙す」という語法の作用領域:eval(…)とwith.1.eval eval関数は文字列パラメータを受け入れ、その内容を書き込み時にプログラムに存在するようなコードと見なすことができます.evalは、1つまたは複数の声明を含む「コード」文字列を演算し、既存の動作領域を修正します.
function foo(str,a){
	eval(str);
	console.log(a, b);			//1 , 3
	console.log(a, window.b);	//1 , 2
}
var b = 2;
foo("var b = 3;", 1);
は、上記のグローバル変数bが上書きされたと説明しています.bはグローバルなので、window.bが取得できます.しかし、大域以外の変数は上書きされたらアクセスできません.
厳しいモードで:
function foo(str,a){
	"use strict";
	eval(str);
	console.log(a, b);			//1 , 2
	console.log(a, window.b);	//1 , 2
}
var b = 2;
foo("var b = 3;", 1);
2.with
withは通常、オブジェクトの複数の属性を繰り返し参照するためのショートカットとして扱われ、参照オブジェクト自体を重複させる必要がない.
withは、オブジェクトの属性を、スコープ内の識別子として処理し、新しいスコープ(実行フェーズ)を作成する.
function foo(obj){
	with(obj){
		a = 2;
	}
}
var o1 = { a : 3 };
var o2 = { b : 3 };

foo(o1);
console.log( o1.a );	// 2

foo(o2);
console.log( o2.a );	// undefined
console.log( a );		// 2,  a          !
この2つの機構の副作用はエンジンがコンパイルできない時に作用領域の検索を最適化できないので、コードの運行速度が遅くなることを招いて、それらを使わないことを提案します.
PS:原理的にブログ「JavaScript言語精粋【粕、毒瘤】」でWithが使えない理由を述べました. 
三、関数のスコープとブロックのスコープ
1.匿名と具名
/*   (          arguments.callee  ) */
setTimeout(function(){
	console.log("i wait 1 second!")
},1000);
/*   (    ) */
setTimeout(function timeoutHandler(){
	console.log("i wait 1 seco nd!")
},1000);
2.直ちに関数式を実行します.
/* IIFE   */
var a = 2;
(function IIFE(global){
	var a = 3;
	console.log(a);			//3
	console.log(global.a);	//2
})(window);
/* UMD   */
var b = 2;
(function UMD(def){
	def(window);
})(function tmpF(global){
	var b = 3;
	console.log(b);			//3
	console.log(global.b);	//2
});
3.ブロックスコープ
try/catchはブロックスコープを作成します.
try{
	undefined();
}catch(err){
	console.log(err);	//      
}
console.log(err);	//ReferenceError: err is not defined

/*  1 */
for(var i=0;i<10;i++){}
console.log(i);		//10
/*  2 */
{
	console.log(bar);	//undefined     !!
	var bar = 2;
}
ES 6に新たなletキーワードを導入!
/*   1 */
for(let i=0;i<10;i++){}
console.log(i);		//SyntaxError: Block-scoped declarations (let, const, function, class) not yet supported outside strict mode
/*   2 */
{
	console.log(bar);	//SyntaxError   !!
	let bar = 2;
}
ES 6コードを互換性ES 6に変換する前の二つの環境(ほとんどES 5ですが、全部ではありません)を推奨します.ツール:Traceurとlet-er