閉包(closure),是指函數變量能夠保存在函數做用域內,所以看起來是函數將變量「包裹」了起來。閉包
//根據定義,包含變量的函數就是閉包 function foo() { var a = 0; } cosole.log(a) // Uncaught ReferenceError: a is not defined
閉包是指有權訪問另外一個函數做用域中的變量的函數函數
//訪問上層函數的做用域的內層函數就是閉包 function foo() { var a = 2; function bar() { console.log(a); } bar(); } foo();
函數對象能夠經過做用域鏈相互關聯起來,函數體內部變量能夠保存在函數做用域內,這就是閉包。優化
var global = "global scope"; //全局變量 function checkscope() { var scope = "local scope"; //局部變量 function f() { return scope; //在做用域中返回這個值 }; return f(); } checkscope(); // 返回 "local scope"
當函數能夠記住並訪問所在的詞法做用域時,就產生了閉包,即便函數是在當前詞法做用域以外執行。設計
//fn3就是fn2函數自己。執行fn3能正常輸出name //這不就是fn2能記住並訪問它所在的詞法做用域,並且fn2函數的運行仍是在當前詞法做用域以外了。 function fn1() { var name = 'iceman'; function fn2() { console.log(name); } return fn2; } var fn3 = fn1(); fn3();
閉包是一種特殊的對象。它由兩部分構成:函數,以及建立該函數的環境。環境由閉包建立時在做用域中的任何局部變量組成。
簡單說就是指那些可以訪問自由變量的函數。code
【1】訪問所在做用域;
【2】函數嵌套;
【3】在所在做用域外被調用對象
Javascript 會找出再也不使用的變量,再也不使用意味着這個變量生命週期的結束。
Javascript 中存在兩種變量——全局變量和局部變量,所有變量的聲明週期會一直持續,直到頁面卸載而局部變量聲明在函數中,它的聲明週期從執行函數開始,直到函數執行結束。在這個過程當中,局部變量會在堆或棧上被分配相應的空間以存儲它們的值,函數執行結束,這些局部變量也再也不被使用,它們所佔用的空間也就被釋放。
可是有一種狀況的局部變量不會隨着函數的結束而被回收,那就是局部變量被函數外部的變量所使用,其中一種狀況就是閉包,由於在函數執行結束後,函數外部的變量依然指向函數內的局部變量,此時的局部變量依然在被使用,因此也就不可以被回收生命週期
var scope = 'global scope'; function checkScope() { var scope = 'local scope'; return function() { console.log(scope); } } var result = checkScope(); result(); // local scope checkScope變量對象中的scope,非全局變量scope
此匿名函數的做用域鏈包括checkScope的活動對象和全局變量對象, 當checkScope函數執行完畢後,checkScope的活動對象並不會被銷燬,由於匿名函數的做用域鏈還在引用checkScope的活動對象,也就是checkScope的執行環境被銷燬,可是其活動對象沒有被銷燬,留存在堆內存中,直到匿名函數銷燬後,checkScope的活動對象纔會銷燬ip
i. 即便建立它的上下文已經銷燬,它仍然存在(好比,內部函數從父函數中返回)
ii. 在代碼中引用了自由變量內存
var scope = "global scope"; function checkscope(){ var scope = "local scope"; function f(){ return scope; } return f; } var foo = checkscope(); foo(); fContext = {//f函數的執行上下文 Scope: [AO, checkscopeContext.AO, globalContext.VO], }
對的,就是由於這個做用域鏈,f函數在聲明的時候壓入了上層的變量對象,f 函數依然能夠讀取到 checkscopeContext.AO 的值,而且若是當 f 函數引用了 checkscopeContext.AO 中的值的時候,即便 checkscopeContext 被銷燬了,可是 JavaScript 依然會讓 checkscopeContext.AO 活在內存中(和垃圾回收機制有關下文會說),f 函數依然能夠經過 f 函數的做用域鏈找到它,正是由於 JavaScript 作到了這一點,從而實現了閉包這個概念。作用域
任何在函數中定義的變量,均可以認爲是私有變量,由於不能在函數外部訪問這些變量。
私有變量包括函數的參數、局部變量和函數內定義的其餘函數。
function module() { var arr = []; function add(val) { if (typeof val == 'number') { arr.push(val); } } function get(index) { if (index < arr.length) { return arr[index] } else { return null; } } return { add: add, get: get } } var mod1 = module(); mod1.add(1); mod1.add(2); mod1.add('xxx'); console.log(mod1.get(2));//外部是沒法直接拿到arr的只能經過get來拿
咱們來實現一個計數器,每調用一次計數器返回值加一:
var counter = 0; function add() { return counter += 1; } add(); add(); add();// 計數器如今爲 3
問題:
那咱們把counter放在add函數裏面不就行了麼?
function add() { var counter = 0; return counter += 1; } add(); add(); add();// 本意是想輸出 3, 但輸出的都是 1
因此這樣作的話,每次調用add函數,counter的值都要被初始化爲0,仍是達不到咱們的目的。
使用閉包來寫就會解決這些問題
function add() { var index = 1; function counter() { return index ++; } return counter; } // test var addA = add() ; var addB = add() ; addA(); // 1 addA(); // 2 addB(); // 1 addB(); // 2
這樣打印出來的所有都是10,緣由是for循環是同步的會在延時1000毫秒的過程當中一直執行
等function執行的時候變量i指向的是同一個內存地址,且值已經變成的10
for (var i = 1; i <= 10; i++) { setTimeout(function () { console.log(i); }, 1000); }
改進,用自執行的函數建立簡單的閉包,讓每一次for循環的i都在不一樣的內存地址中且不被銷燬
for (var i = 1; i <= 10; i++) { (function () { var j = i; setTimeout(function () { console.log(j); }, 1000); })(); }
優化寫法
for (var i = 1; i <= 10; i++) { (function (j) { setTimeout(function () { console.log(j); }, 1000); })(i); }
閉包的做用主要就是讓變量的值始終保持在內存中。
C++或C語言還有Java中都有static靜態變量也是讓變量始終保存在內存中。
這樣來看好像閉包好像有點static靜態變量的意思。
閉包就是子函數能夠有權訪問父函數的變量、父函數的父函數的變量、一直到全局變量。 歸根結底,就是利用js得詞法(靜態)做用域,即做用域鏈在函數建立的時候就肯定了。 子函數若是不被銷燬,整條做用域鏈上的變量仍然保存在內存中,這樣就造成了閉包