Scopes
JS的作用域是靜態作用域(static scope
),也可以稱為詞法作用域(lexical scope
).其主要特征在于,函數作用域中遇到既不是形參也不是函數內部定義的局部變量時,去函數定義時上下文中查。 而與之相對應的是動態作用域(dynamic scope
)則不同,其函數作用域中遇到既不是形參也不是函數內部定義的局部變量時,到函數調用時的上下文中去查。
舉個例子:
var x = 3;
function fun1() {
alert(x);
}
(function(){
var x = 6;
fun1();
})()
js中alert
出的是3,若是動態作用域則會alert
6;
現有語言中使用靜態作用域的有:C
, C++
,Python
, Java
, JavaScript
, Lua
等。。。 大多數現在程序設計語言都是采用靜態作用域規則
現有語言中使用動態作用域的有:Emacs Lisp
, Common Lisp
(兩種都有), Perl
(兩種都有)
Scope Chains
作用域鏈的產生是由于函數或者流程控制語句的嵌套,如:
function outer (){
var foo = 1;
function inner() {
var bar = 2;
}
}
那此時 outer
中可使用foo
,inner
中能使用 foo
和 bar
。顯而易見:里層可以使用外層定義的變量,反之不然。
但有種情況需要注意: 當里層函數所定義的變量名和外層相同時。里層所定義的變量會覆蓋掉外層的,但不會影響外層的值。
Closures
閉包在node
回調的實現中表現的尤為突出,并且他為js大多數的異步任務提供了極好的機制。
舉個簡單的例子來大致了解下閉包。
先定義一個someFunc()
函數,然后再函數里定義一個bar
變量。那對應的作用域如下
someFunc()
var bar
↑
?
然后在someFunc()
內部定義函數inner()
并且在inner()
中使用bar
,作用域鏈如下:
someFunc()
var bar
↑
|
inner()
console.log(bar)
↑
?
此時inner()
函數持有了外部函數的bar變量。inner()
就是個閉包
支撐著callback
式的編程實現的是,不論inner()
函數是否立馬被調用,這個閉包都不會被釋放。而且在js里,無論把inner()
函數作為參數傳遞給別的函數,或者作為返回值返回,以便于之后調用,都是合法的。前面說過inner()
持有著bar
變量,所以bar
變量也是一直能被使用的狀態,不會被釋放。
Garbage Collection
在js里,內存是被runtime
管理著的。runtime
決定著所有內存的是否釋放和釋放時間。這個管理內存的流程就是GC-垃圾回收
。
每個js的runtime
都有自己的垃圾回收算法,但是最常用的是一種標記&清理(Mark&Sweep)
的算法。這個算法的體思想是,標記那些可以通過代碼獲得值的內存引用(如:變量,函數,等。。), 任何沒有被標記的內存塊都會被清理。
這種標記可用內存的概念與閉包息息相關。比如:
someFunc()
var bar
return inner
↑
|
inner()
console.log(bar)
↑
?
當inner()
這個閉包從someFunc()
返回的時候,它始終維持著對bar
的引用。所以bar
所在的內存會被標記,這塊內存也不會被CG清理了。
不僅bar
不會被清理,bar
所在的作用域也不會被清理,因為程序需要根據作用域來找到定義bar
的環境。
只有當inner()
不再需要的時候,inner()
才會被釋放,同理bar
也會被標記為可釋放,作用域鏈就能順利被釋放了。
到此,作用域、作用域鏈、閉包、垃圾回收就都具有了一定的關聯性,理解起來也可以全面點了。
本文只是做了個大致的介紹,每個模塊要深入了解的東西還非常多,而且現在網上資料也非常全。僅僅在此做個大致的梳理。