做用域
變量做用域的類型:全局變量和局部變量
全局做用域
對於最外層函數定義的變量擁有全局做用域,即對任何內部函數來講,都是能夠訪問的數組
<script> var outerVar = "outer"; function fn(){ console.log(outerVar); } fn();//result:outer </script>
局部做用域
和全局用域相反,局部做用域通常只在固定的代碼片斷內可訪問到,對於函數外部是沒法訪問的閉包
<script> function fn(){ var innerVar = "inner"; } fn(); console.log(innerVar);// ReferenceError: innerVar is not defined </script>
注意
須要注意的是,函數內部聲明變量的時候,必定要使用var命令。若是不用的話,你實際上聲明瞭一個全局變量!函數
<script> function fn(){ innerVar = "inner"; } fn(); console.log(innerVar);// result:inner </script>
做用域鏈
個人理解就是,根據在內部函數能夠訪問外部函數變量的這種機制,用鏈式查找哪些數據能被內部函數訪問
執行環境
每一個函數在運行時都會產生一個執行環境。js爲每一個執行環境關聯了一個變量對象。環境中定義的全部變量和函數都保存在這個對象中
做用域鏈理解性能
<script> var scope = "global"; function fn1(){ return scope; } function fn2(){ return scope; } fn1(); fn2(); </script>
當某個函數第一次被調用時,就會建立一個執行環境(execution context)以及相應的做用域鏈,並把做用域鏈賦值給一個特殊的內部屬性([scope])。而後使用this,arguments(arguments在全局環境中不存在)和其餘命名參數的值來初始化函數的活動對象(activation object)。當前執行環境的變量對象始終在做用域鏈的第0位。
以上面的代碼爲例,當第一次調用fn1()時的做用域鏈以下圖所示: this
能夠看到fn1活動對象裏並無scope變量,因而沿着做用域鏈(scope chain)向後尋找,結果在全局變量對象裏找到了scope,因此就返回全局變量對象裏的scope值。spa
閉包code
閉包有兩個做用:
第一個就是能夠讀取自身函數外部的變量(沿着做用域鏈尋找)
第二個就是讓這些外部變量始終保存在內存中 對象
關於第二點,來看一下如下的代碼:blog
<script> function outer(){ var result = new Array(); for(var i = 0; i < 2; i++){//注:i是outer()的局部變量 result[i] = function(){ return i; } } return result;//返回一個函數對象數組 //這個時候會初始化result.length個關於內部函數的做用域鏈 } var fn = outer(); console.log(fn[0]());//result:2 console.log(fn[1]());//result:2 </script>
返回結果很出乎意料吧,你確定覺得依次返回0,1,但事實並不是如此
來看一下調用fn[0]()
的做用域鏈圖: ip
能夠看到result[0]函數的活動對象裏並無定義i這個變量,因而沿着做用域鏈去找i變量,結果在父函數outer的活動對象裏找到變量i(值爲2),而這個變量i是父函數執行結束後將最終值保存在內存裏的結果。
由此也能夠得出,js函數內的變量值不是在編譯的時候就肯定的,而是等在運行時期再去尋找的。
那怎麼才能讓result數組函數返回咱們所指望的值呢?
<script> function outer(){ var result = new Array(); for(var i = 0; i < 2; i++){ //定義一個帶參函數 function arg(num){ function innerarg(){ return num; } return innerarg; } //把i當成參數傳進去 result[i] = arg(i); } return result; } var fn = outer(); console.log(fn[0]()); console.log(fn[1]()); </script>
由上圖可知,當調用innerarg()時,它會沿做用域鏈找到父函數arg()活動對象裏的arguments參數num=0.
上面代碼中,函數arg在outer函數內預先被調用執行了,對於這種方法,js有一種簡潔的寫法
function outer(){ var result = new Array(); for(var i = 0; i < 2; i++){ //定義一個帶參函數 result[i] = function(num){ function innerarg(){ return num; } return innerarg; }(i);//預先執行函數寫法 //把i當成參數傳進去 } return result; }
使用閉包的注意點
1)因爲閉包會使得函數中的變量都被保存在內存中,內存消耗很大,因此不能濫用閉包,不然會形成網頁的性能問題,在IE中可能致使內存泄露。解決方法是,在退出函數以前,將不使用的局部變量所有刪除。
2)閉包會在父函數外部,改變父函數內部變量的值。因此,若是你把父函數看成對象(object)使用,把閉包看成它的公用方法(Public Method),把內部變量看成它的私有屬性(private value),這時必定要當心,不要隨便改變父函數內部變量的值。