1.能夠讀取函數內部的變量。面試
2.讓這些變量的值始終保持在內存中,變量或參數不會被垃圾回收機制回收GC。bash
當想要的獲得f1函數的局部變量時,正常狀況下,這是辦不到的,只有經過變通方法才能實現。那就是在函數的內部,再定義一個函數閉包
function f1() {
var n = 999;
function f2() {
console.log(n); // 999
}
}
複製代碼
上面代碼中,函數f2就在函數f1內部,這時f1內部的全部局部變量,對f2都是可見的。可是反過來就不行,f2內部的局部變量,對f1就是不可見的。這就是 JavaScript 語言特有的」鏈式做用域」結構(chain scope),子對象會一級一級地向上尋找全部父對象的變量。因此,父對象的全部變量,對子對象都是可見的,反之則不成立。既然f2能夠讀取f1的局部變量,那麼只要把f2做爲返回值,咱們不就能夠在f1外部讀取它的內部變量了嗎!
函數
function f1() {
var n = 999;
function f2() {
console.log(n);
}
return f2;
}
var result = f1();
result(); // 999
複製代碼
上面代碼中,函數f1的返回值就是函數f2,因爲f2能夠讀取f1的內部變量,因此就能夠在外部得到f1的內部變量了。閉包就是函數f2,即可以讀取其餘函數內部變量的函數。因爲在 JavaScript 語言中,只有函數內部的子函數才能讀取內部變量,所以能夠把閉包簡單理解成「定義在一個函數內部的函數」。在本質上,閉包就是將函數內部和函數外部鏈接起來的一座橋樑。
性能
閉包的最大用處有兩個,一個是能夠讀取函數內部的變量,另外一個就是讓這些變量始終保持在內存中ui
請看下面的例子,閉包使得內部變量記住上一次調用時的運算結果。this
function zxxs(zxx) {
return function () {
return zxx++;
};
}
var inc = zxxs(5);
inc() // 5
inc() // 6
inc() // 7
複製代碼
上面代碼中,zxx是函數zxxs的內部變量。經過閉包,zxx的狀態被保留了,每一次調用都是在上一次調用的基礎上進行計算。從中能夠看到,閉包inc使得函數zxxs的內部環境,一直存在。因此,閉包能夠看做是函數內部做用域的一個接口。爲何會這樣呢?緣由就在於inc始終在內存中,而inc的存在依賴於zxxs,所以也始終在內存中,不會在調用結束後,被垃圾回收機制回收。
spa
閉包的另外一個用處,是封裝對象的私有屬性和私有方法:.net
function Person (name) {
var age
function setAge (n) {
age = n
}
function getAge () {
return age
}
return {
name: name,
getAge: getAge,
setAge: setAge
}
}
var p1 = Person('zxx')
p1.setAge(18)
p1.getAge() // 18
複製代碼
上面代碼中,函數Person
的內部變量age
,經過閉包getAge
和setAge
,變成了返回對象p1
的私有變量。
3d
注意,外層函數每次運行,都會生成一個新的閉包,而這個閉包又會保留外層函數的內部變量,因此內存消耗很大。所以不能濫用閉包,不然會形成網頁的性能問題。
函數執行完後,函數內的局部變量沒有釋放,佔用內存時間會變長,容易形成內存泄漏(內存被佔用,可是沒有用上)
function fn1 () {
var arr = new Array[1000]
function fn2 () {
console.log(arr.length)
}
return fn2
}
var f = fn1() // 已經產生閉包
f()
f = null // 讓內部函數成爲垃圾對象-->回收閉包
複製代碼
習題1(this指向詳解)
var name = 'The Window'
var object = {
name: 'My Object',
getNameFunc: function () {
return function () {
return this.name
}
}
}
alert(object.getNameFunc()()) //The Window
object.getNameFunc() // 執行完變成一個函數,this指向window, 關於this的指向後續會更新新的文章詳解
var name = 'The Window'
var object = {
name: 'My Object',
getNameFunc: function () {
var that = this
return function () {
return that.name
}
}
}
alert(object.getNameFunc()()) // My Object
複製代碼
習題2
function outerFun(){
var a=0;
function innerFun() {
a++;
alert(a);
}
return innerFun;
}
var obj = outerFun();
obj(); // 結果爲1
obj(); // 結果爲2
var obj2 = outerFun();
obj2(); // 結果爲1
obj2(); // 結果爲2
複製代碼
習題3
function foo (x) {
var tmp = 3
function bar (y) {
console.log(x + y + (++tmp))
}
bar(10)
}
foo(2) // 16
foo(2) // 16
foo(2) // 16
foo(2) // 16
這裏只是函數調用,不是閉包
========================
function foo (x) {
var tmp = 3
return function (y) {
console.log(x + y + (++tmp))
}
}
var bar = foo(2)
bar(10) // 16
bar(10) // 17
bar(10) // 18
bar(10) // 19
當你return的是內部function時,就是一個閉包。
一個函數訪問了它的外部變量,那麼它就是一個閉包
複製代碼
一道經典的閉包面試題
function fun(n,o){
console.log(o);
return {
fun:function(m){
return fun(m,n);
}
}
}
var a = fun(0); // undefined
a.fun(1); // 0
a.fun(2); // 0
a.fun(3); // 0
var b=fun(0).fun(1).fun(2).fun(3); //undefined,0,1,2
var c=fun(0).fun(1); //undefined,0
c.fun(2); // 1
c.fun(3); // 1
複製代碼
詳解:
轉換爲等價代碼
return返回的對象的fun屬性對應一個新建的函數對象,
這個函數對象將造成一個閉包做用域,
使其可以訪問外層函數的變量n及外層函數fun,
爲了避免將fun函數和fun屬性搞混,咱們將上述代碼修改以下:
function _fun_(n,o){
console.log(o);
return {
fun:function(m){
return _fun_(m,n);
}
}
}
var a = fun(0); // undefined 產生了閉包 n = 0
a.fun(1); // 0 產生了閉包,可是因爲沒有綁定變量,閉包立刻就消失了, 始終用的是a裏面的閉包
a.fun(2); // 0 產生了閉包,可是因爲沒有綁定變量,閉包立刻就消失了, 始終用的是a裏面的閉包
a.fun(3); // 0 產生了閉包,可是因爲沒有綁定變量,閉包立刻就消失了, 始終用的是a裏面的閉包
var b = fun(0).fun(1).fun(2).fun(3); // undefined,0,1,2 會不斷產生新的閉包
等價代碼
var b1 = b.fun(1);
var b2 = b1.fun(2);
var b3 = b2.fun(3);
var c = fun(0).fun(1); // undefined,0,
c.fun(2); // 1 當前c的閉包是 1
c.fun(3); // 1 當前c的閉包是 1
雖然c.fun(2)和c.fun(3)都產生了新的閉包,可是因爲沒有賦給新的變量,閉包接着就消失了
複製代碼