本文是 重溫基礎 系列文章的第十九篇。
今日感覺:將混亂的事情找出之間的聯繫,也是種能力。 html
系列目錄:前端
本章節複習的是JS中的關於閉包,這個小哥哥呀,看看。 git
前置知識:
聲明函數兩種方法:github
fun(); // ok function fun(){};
fun(); // error var fun = function (){};
這裏先要了解一個概念,詞法做用域:它是靜態的做用域,是書寫變量和塊做用域的做用域**。segmentfault
function f (){ var a = "leo"; function g(){console.log(a)}; g(); } f(); // "leo"
因爲函數g
的做用域中沒有a
這個變量,可是它能夠訪問父做用域,並使用父做用域下的變量a
,最後輸出"leo"
。 數組
詞法做用域中使用的域,是變量在代碼中聲明的位置所決定的。嵌套的函數能夠訪問在其外部聲明的變量。微信
接下來介紹下閉包概念,閉包是指有權訪問另外一個函數做用域中的變量的函數。 閉包
閉包是由函數以及建立該函數的詞法環境組合而成。這個環境包含了這個閉包建立時所能訪問的全部局部變量。 函數
建立閉包的常見方式:在一個函數內建立另外一個函數。如:post
function f (){ var a = "leo"; var g = function (){ console.log(a); }; return g;// 這裏g就是一個閉包函數,能夠訪問到g做用域的變量a } var fun = f(); fun(); // "leo"
經過概念能夠看出,閉包有如下三個特徵:
注:關於內存回收機制,能夠查看阮一峯老師的《JavaScript 內存泄漏教程》。
另外,使用閉包有如下好處:
function f (){ var a = 1; return function(){ a++; console.log(a); } } var fun = f(); fun(); // 2 fun(); // 3
由於垃圾回收機制沒有回收,因此每次調用fun()
都會返回新的值。
function f (){ var a = 1; function f1 (){ a++; console.log(a); }; function f2 (){ a++; console.log(a); }; return {g1:f1, g2:f2}; }; var fun = f(); fun.g1(); // 2 fun.g2(); // 3
function f (){ var a = []; for(var i = 0; i<10; i++){ a[i] = function(){ console.log(i); } } return a; } var fun = f(); fun[0](); // 10 fun[1](); // 10 // ... fun[10](); // 10
本來照咱們的想法,fun
方法中每一個元素上的方法執行的結果應該是1,2,3,...,10
,而實際上,每一個返回都是10
,由於每一個閉包函數引用的變量i
是f
執行環境下的變量i
,循環結束後,i
已經變成10
,因此都會返回10
。
解決辦法能夠這樣:
function f (){ var a = []; for(var i = 0; i<10; i++){ a[i] = function(index){ return function(){ console.log(index); // 此時的index,是父函數做用域的index, // 數組的10個函數對象,每一個對象的執行環境下的index都不一樣 } }(i); }; return a; }; var fun = f(); fun[0](); // 0 fun[1](); // 1 // ... fun[10](); // 10
var obj = { name : "leo", f : function(){ return function(){ console.log(this.name); } } } obj.f()(); // undefined
因爲裏面的閉包函數是在window
做用域下執行,所以this
指向window
。
當咱們在閉包內引用父做用域的變量,會使得變量沒法被回收。
function f (){ var a = document.getElementById("leo"); a.onclick = function(){console.log(a.id)}; }
這樣作的話,變量a
會一直存在沒法釋放,相似的變量愈來愈多的話,很容易引發內存泄漏。咱們能夠這麼解決:
function f (){ var a = document.getElementById("leo"); var id = a.id; a.onclick = function(){}; a = null; //主動釋放變量a }
經過把變量賦值成null
來主動釋放掉。
代碼以下:
for(var i = 0 ; i<10; i++){ setTimeout(function(){ console.log(i); },100); }
不出所料,返回的不是咱們想要的0,1,2,3,...,9
,而是10個10
。
這是由於js是單進程,因此在執行for循環
的時候定時器setTimeout
被安排到任務隊列中排隊等候執行,而在等待過程當中,for循環
已經在執行,等到setTimeout
要執行的時候,for循環
已經執行完成,i
的值就是10
,因此就打印了10個10
。
解決方法 :
let
。把for循環
中的var
替換成let
。
for(var i = 0; i<10 ; i++){ (function(i){ setTimeout(function(){ console.log(i); }, i*100); })(i); }
function f(num){ return num >1 ? num*f(num-1) : 1; } var fun = f; f = null; fun(4) // 報錯 ,由於最好是return num* arguments.callee(num-1),arguments.callee指向當前執行函數,可是在嚴格模式下不能使用該屬性也會報錯,因此藉助閉包來實現
這裏可使用return num >1 ? num* arguments.callee(num-1) : 1;
,由於arguments.callee
指向當前執行函數,可是在嚴格模式下不能使用,也會報錯,因此這裏須要使用閉包來實現。
function fun = (function f(num){ return num >1 ? num*f(num-1) : 1; })
這樣作,實際上起做用的是閉包函數f
,而不是外面的fun
。
ES6以前,使用var
聲明變量會有變量提高問題:
for(var i = 0 ; i<10; i++){console.log(i)}; console.log(i); // 變量提高 返回10
爲了不這個問題,咱們這樣使用閉包(匿名自執行函數):
(function(){ for(var i = 0 ; i<10; i++){console.log(i)}; })() console.log(i); // undefined
咱們建立了一個匿名的函數,並當即執行它,因爲外部沒法引用它內部的變量,所以在函數執行完後會馬上釋放資源,關鍵是不污染全局對象。這裏i
隨着閉包函數的結束,執行環境銷燬,變量回收。
可是如今,咱們用的更多的是ES6規範的let
和const
來聲明。
本部份內容到這結束
Author | 王平安 |
---|---|
pingan8787@qq.com | |
博 客 | www.pingan8787.com |
微 信 | pingan8787 |
每日文章推薦 | https://github.com/pingan8787... |
JS小冊 | js.pingan8787.com |
歡迎關注微信公衆號【前端自習課】天天早晨,與您一塊兒學習一篇優秀的前端技術博文 .