範畴有两种模型:
语彙範畴(Lexical Scope)动态範畴(Dynamic Scope)Lex-time
语彙範畴(lexical scope)是在lexing time时期所定义的範畴。
换句话说,语彙範畴(lexical scope)是基于撰写程式码与程式区块的实际位置所定义出来的。
example
function foo(a) { var b = a * 2; function bar(c) { console.log(a, b, c); } bar(b * 3);}foo(2); // 2 4 12
这个範例,有3个巢状範畴(nested scopes):
图片来源:You Don't Know JS: Scope & Closures
foo
。scope2:函式foo
的範畴,里面包含参数a
,区域变数b
,函式bar
。scope3:函式bar
的範畴,里面包含参数c
。上面的图并不是文氏图(Venn diagram)。函式是无法同时存在于2个同层级的範畴。
Look-ups
上面範例的执行过程:
Engine执行foo(2);
,接着执行bar(b * 3);
,最后执行console.log(a, b, c);
。这时需要对a
,b
,c
执行RHS,所以Engine会从最内层的範畴开始搜寻。a
,b
不在bar
的範畴中,所以Engine会往外层的範畴(foo
)找,并且找到。在呼叫bar
的同时,b * 3
的运算结果,作为引数,传给c
。Look-ups的动作,会在找到第一个符合的时候,就停止。
如果相同的识别子(identifier)名称,分别定义在不同层级的範畴,会发生shadowing的情况。
「shadowing」意思是,内层範畴的识别子遮住了外层範畴同名的识别子(identifier),换句话说,只要在当时执行的範畴中找到符合的识别子(identifier),Look-ups的动作就会停止,不管外层是否有同名的识别子(identifier)都不会採用。
无论是否有shadowing的情况。Engine执行Look-ups的动作,都会从当下的範畴开始执行,视情况再往外层推移。
全域变数(global variables)本身会成为全域物件(global object)window
的属性。
我们可以藉由此特性,来存取被遮蔽的全域变数。
全域变数被遮蔽:
var c = 10;function foo(a) { var b = a * 2; var c = 5; function bar(c) { console.log(a, b, c); } bar(c);}foo(2); //2 4 5
使用全域物件取得属性值:
var c = 10;function foo(a) { var b = a * 2; var c = 5; function bar(c) { console.log(a, b, c); } bar(window.c);}foo(2); //2 4 10
但如果被遮蔽的是非全域变数(non-global)的话,就无法使用此方法。
不管在何处呼叫函式,也不管是如何呼叫的,该函式在宣告(declaration)的时候,就已经决定其语彙範畴(lexical scope)了,不会改变。
重点整理
语彙範畴(Lexical scope)在宣告函式的时候就已经定义好了,也就是实际撰写程式码的位置。
不要使用eval & with。
参考来源:
此为You Don't Know JS系列的笔记。