當咱們新建一個能夠儲存變量的值,怎麼才能讀取到這個變量呢?能訪問到這個變量,就說明符合做用域規則,做用域規則就能夠說是js引擎讀取變量的規則。
在js中變量分爲兩種,全局變量和局部變量,全局變量(擁有全局做用域)能夠在整個js應用中被全部代碼訪問到,從程序開始分配內存直至結束纔會被釋放(出於對代碼的性能、可讀性、以及潛在衝突考慮應該減小使用)。
局部變量在函數中聲明的變量,函數的參數(做用域是局部性的),在函數體外,或者說的當前做用域的上層是沒法直接讀取的。
下面再說下做用域嵌套,產生做用域鏈的事,在咱們實際編寫代碼的時候,好比:閉包
var a = 'a'; function demo(b) { let c = 'c'; function print(c) { console.log(a + ' ' + b + ' ' + c); } print(c); } demo('b');
執行結果:函數
在print方法執行的時候,會從當前的執行做用域開始查找變量,若是找不到,就繼續向上一級繼續查找,若是找到則會中止,若是沒有就會繼續直至當最後到達全局做用域時,此時不管找到仍是沒找到,都會結束查找。性能
st=>start: 當前做用域 io1=>start: 上層做用域 io2=>start: ... cond=>end: 全局做用域 st->io1->io2->cond
(簡單寫了個流程圖方便腦補)spa
接下來仍是這段代碼讓咱們來作一個小小的修改。code
var a = 'a'; function demo(b) { let c = 'c', a = 'd'; function print(c) { console.log(a + ' ' + b + ' ' + c); } print(c); } demo('b');
執行結果:ip
結果是輸出了上一層的變量a,這也就是做用域查找的機制,做用域始終從運行時所處的最內部做用域開始,逐漸向外層查找,當遇到第一個符合條件的結果就會返回,中止查找。內存
先簡單上一段代碼作用域
a = 'a'; var a; console.log(a);
執行結果:
爲何會有這種結果呢?聲明在後,反而打印出前面聲明得值,其實js在編譯的時候會先進行聲明操做,以後再進行賦值操做,也就是隻要是在做用域中的聲明一出現,就將在代碼自己被執行前優先進行處理。也能夠將這個過程理解爲全部的聲明(變量和函數)都會被「提高」到做用域的最頂端。(雖然函數聲明和變量聲明都會被提高,可是函數優先級要高於變量)開發
(在ES6引入了let和const能夠用來聲明建立塊做用域。for循環頭部定義強烈建議用let,感興趣的人能夠去看看js函數做用域和塊做用域)it
提到閉包不少人腦殼裏都會感受模模糊糊的,閉包在概念上定義是可以讀取其餘函數內部變量的函數,在中,只有函數內部的子函數才能讀取這個函數局部變量,這時候前面說的做用域起到做用了,因此咱們如今能夠這樣理解,定義在一個函數內部的函數,這個函數能夠訪問其餘函數的變量,就能夠稱爲「閉包」。
下面來聊聊閉包的用處,主要就是。1.可以讀取到函數內部的變量,2.可讓變量始終保存在內存中(這點要注意後面解釋),下面上代碼。
function demo() { let a = 'a'; function print() { console.log(a); } return print; } var clo = demo(); clo();
執行結果:
這樣咱們就在外層取得了demo函數內部局部變量的a,也就是閉包實現從外部讀取局部變量的能力。
下面說一說讓變量始終保存在內存中這點.
function demo() { let a = 1; addA = () => a+=1; function print() { console.log(a); } return print; } var clo = demo(); clo(); addA(); clo();
執行結果:
閉包能夠阻止,demo內部做用域都被垃圾清理機制回收銷燬,能夠看到局部變量a第二次執行比第一次+1,這就說明第一次運行結束後啊,沒有被回收,由於print()仍在使用這個做用域因此demo不會被回收。
(閉包會使得對應的變量一直保存在內存中,不會被清理回收,對內存消耗大,別濫用)
1.原生的setTimeout傳遞的函數不能帶參數
//會報錯 function func(param){ console.log(param); } var test = func(1); setTimeout(test, 1000); //經過閉包實現傳參效果 function func(param){ return function(){ console.log(param); } } var test = func(1); setTimeout(test, 1000);
2.封裝變量