引言
在寫業務代碼的同時,我覺得還是很需要把基礎知識全部理清楚的,閉包和作用域呢我一直覺得我懂了,那麼試試看能不能說清楚?當然我也會參考一下別人說法,所以會在參考資料裏面寫上啦嘻嘻~~
作用域和作用域鏈
作用域是一個語言無關的概念,當然作用域分爲詞法作用域和動態作用域
作用域:通常來說,一段程序代碼中所用的名字並不總是有效/可用的,而限定這個名字的可用性的代碼範圍就是這個名字的作用域。
詞法作用域:也叫做靜態作用域,它的作用域是指在詞法分析階段就確定了,是不會改變的。
動態作用域:是在運行的時候根據程序的流程信息來動態確定的,而不是寫代碼是進行靜態確定的。
var a = 2;
function foo() {
console.log(a); // 會輸出2還是3?
}
function bar() {
var a = 3;
foo();
}
bar();
如果是詞法作用域
foo()
函數引用到全局作用域中的a,因此會輸出2。因爲詞法作用域是寫代碼的時候就靜態確定下來的,JavaScript的作用域就是詞法作用域(大部分語言也都是基於詞法作用域的),所以這段大媽輸出應該是2
.
如果是動態作用域
動態作用域不會關心函數和作用域是如何聲明以及在何處聲明的,只關心它們從何處調用。換句話說,作用域鏈就是基於調用棧的,而不是代碼中的作用域嵌套,因此,如果是JavaScript具有動態作用域,理論上輸出結果應該是3
.
總結
JavaScript並不具有動態作用域,它只有詞法作用域。eval()
、with
、this
機制在某種程度上很像動態作用域,使用上要特別注意。
詞法作用域關注函數在何處聲明,而動態作用域是在運行時確定的(javascript中的this也是)。
閉包
閉包的創造條件
- 存在內、外兩層函數
- 內層函數對外層函數的局部變量進行了引用
閉包是對作用域的眼神,也是在實際開發中經常使用的一個特性。
一個例子:
var scope = {};
if (scope instanceof Object) {
var j = 1;
for (var i = 0; i < 10; i++) {
//console.log(i);
}
console.log(i); //輸出10
}
console.log(j);//輸出1
}
無論用過何種手段將內部函數傳遞到所在的所在的詞法作用域以外,它都會持有對原始定義作用域的引用,無論在何處執行這個函數都會使用閉包
一個難一點的例子:
function wait(message) {
setTimeout( function timer() {
console.log( message );
}, 1000 );
}
wait( "Hello, closure!" );
這也是閉包。傳入的timer
函數一定會被執行,知識內部引擎調用執行,深入到引擎的內部,內置的工具函數setTimeout(..)
持有對一個參數的引用,引擎會調用這個函數!!!
IIFE(立即執行函數)是閉包嗎
它創建了閉包,在內存中創建了一塊區域,這塊區域保存着作用域鏈上的作用域引用。等同創建了閉包
// 它需要有自己的變量,用來在每個迭代中儲存i 的值:
for (var i=1; i<=5; i++) {
(function() {
var j = i;
setTimeout( function timer() {
console.log( j );
}, j*1000 );
})();
}
// 行了!它能正常工作了!。
// 可以對這段代碼進行一些改進:
for (var i=1; i<=5; i++) {
(function(j) {
setTimeout( function timer() {
console.log( j );
}, j*1000 );
})( i );
}
//當然你也可以這樣寫
for (var i=1; i<=5; i++) {
(function(i) {
setTimeout( function timer() {
console.log( i );
}, i*1000 );
})( i );
}
//你也可以這樣
function timeManage() {
for (var i = 0; i < 10; i++) {
setTimeout((function(e) {
return function() {
console.log(e);
}
})(i), 1000)
}
}
//timeManager();輸出1,2,3,4,5
function createClosure() {
var result = [];
for (var i = 0; i < 5; i++) {
result[i] = function(num) {
return function() {
console.log(num);
}
}(i);
}
return result;
}
//createClosure()[1]()輸出1;createClosure()[2]()輸出2
由於閉包會額外的附帶函數的作用域(內部匿名函數攜帶外部函數的作用域),因此,閉包會比其它函數多佔用些內存空間,過度的使用可能會導致內存佔用的增加。
總的來說注意三個問題:
- 變量
- this
- 內存
閉包中的this問題:
閉包中的this之前講過,是特例,跟動態作用域差不多:
var object = {
scope:"local",
getScope:function(){
return function(){
return this.scope;
}
}
}
//object.getScope()()返回值爲global
匿名函數的this永遠指向windows
預編譯
JavaScript腳本的運行其實是由兩個階段組成的:
- 預編譯階段:在內存中開闢出一塊空間,用來存放變量和函數,爲使用
var
和function
關鍵字開闢出一片空間,用來存放這二者聲明的變量和函數。
-預編譯的時候function
的優先級高於var
(也說明了函數是一等公民~~~)
預編譯時不會對變量進行賦值(不會進行初始化),賦值是在執行階段進行的。 - 執行階段
結語
嚶嚶嚶終於寫完了,今天一天特別不在狀態,暈暈乎乎的,所以寫出來的東西可能有點亂,我原來的目的是爲了讓自己方便學習使用,如果大家看到了之後覺得有問題,或者想噴我嚶嚶嚶?評論區是你們的舞臺嗚嗚嗚嗚嗚~~我這麼可愛,別噴我,可以留言我很可愛呀~對了,如果覺得我講的不清楚,都可以看參考資料的,我覺得講得很到位欸
參考資料
詞法作用域 VS 動態作用域
深入javascript——作用域和閉包
你不知道的JS(2)深入瞭解閉包
JavaScript - 預編譯