JavaScript之閉包

閉包

MDN
面試官問我什么是閉包該如何回答
廖雪峰博客-閉包
阮一峰博客-閉包

個人理解

內(nèi)部函數(shù)可以訪問外部函數(shù)的作用域, 如果在內(nèi)部函數(shù)持有了外部函數(shù)的變量等, 并將內(nèi)部函數(shù)return出去,導致這個變量不能被銷毀

function outer() {
    var a= 1;  // 定義一個內(nèi)部變量
    return function () {
        return a; // 返回a的變量值
    }
}

var b = outer()

console.log(b()) ===> 1

產(chǎn)生一個閉包

閉包的作用域鏈包含著它自己的作用域柜某,以及包含它的函數(shù)的作用域和全局作用域耕魄。

function func() {
    let a = 1, b = 2
    function closure() { // 閉包
        return a + b // 返回a+b的值
    }
    return closure // 返回閉包函數(shù)
}

閉包的注意事項

通常囤萤,函數(shù)的作用域及其所有變量都會在函數(shù)執(zhí)行結束后被銷毀。但是酌伊,在創(chuàng)建了一個閉包以后,這個函數(shù)的作用域就會一直保存到閉包不存在為止卧土。

function makeAddr(x) {
    return function(y) {
        return x+y
    }
}

var add5 = makeAddr(5)
var add10 = makeAddr(10)

console.log(add5(2))  ===> 7
console.log(add10(10)) ===> 12

<!-- 釋放對閉包的引用 -->
add5 = null
add10 = null
function f1(){

    var n=999;

    nAdd=function(){n+=1}

    function f2(){
      alert(n);
    }

    return f2;

  }

  var result=f1();

  result(); // 999

  nAdd();

  result(); // 1000

在這段代碼中盹舞,result實際上就是閉包f2函數(shù)。它一共運行了兩次倡蝙,第一次的值是999九串,第二次的值是1000。這證明了寺鸥,函數(shù)f1中的局部變量n一直保存在內(nèi)存中猪钮,并沒有在f1調(diào)用后被自動清除。
為什么會這樣呢析既?原因就在于f1是f2的父函數(shù)躬贡,而f2被賦給了一個全局變量谆奥,這導致f2始終在內(nèi)存中眼坏,而f2的存在依賴于f1,因此f1也始終在內(nèi)存中酸些,不會在調(diào)用結束后宰译,被垃圾回收機制(garbage collection)回收。
這段代碼中另一個值得注意的地方魄懂,就是"nAdd=function(){n+=1}"這一行沿侈,首先在nAdd前面沒有使用var關鍵字,因此nAdd是一個全局變量市栗,而不是局部變量缀拭。其次咳短,nAdd的值是一個匿名函數(shù)(anonymous function),而這個匿名函數(shù)本身也是一個閉包蛛淋,所以nAdd相當于是一個setter咙好,可以在函數(shù)外部對函數(shù)內(nèi)部的局部變量進行操作。

注意點

  1. 由于閉包會使得函數(shù)中的變量都被保存在內(nèi)存中褐荷,內(nèi)存消耗很大勾效,所以不能濫用閉包,否則會造成網(wǎng)頁的性能問題叛甫,在IE中可能導致內(nèi)存泄露层宫。解決方法是,在退出函數(shù)之前其监,將不使用的局部變量全部刪除萌腿。

  2. 閉包會在父函數(shù)外部,改變父函數(shù)內(nèi)部變量的值抖苦。所以哮奇,如果你把父函數(shù)當作對象(object)使用,把閉包當作它的公用方法(Public Method)睛约,把內(nèi)部變量當作它的私有屬性(private value)鼎俘,這時一定要小心,不要隨便改變父函數(shù)內(nèi)部變量的值辩涝。

  3. 返回的函數(shù)并沒有立即執(zhí)行贸伐,而是直到調(diào)用了才執(zhí)行

function count() {
    var arr = [];
    for (var i=1; i<=3; i++) {
        arr.push(function () {
            return i * i;
        });
    }
    return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];

<!-- 
    期望值為 1 4 9 
    實際輸出入下
-->

f1() ===> 16
f2() ===> 16
f3() ===> 16

將var改成let即可正確輸出

全部都是16!原因就在于返回的函數(shù)引用了變量i怔揩,但它并非立刻執(zhí)行捉邢。等到3個函數(shù)都返回時,它們所引用的變量i已經(jīng)變成了4商膊,因此最終結果為16伏伐。返回閉包時牢記的一點就是:返回函數(shù)不要引用任何循環(huán)變量,或者后續(xù)會發(fā)生變化的變量晕拆。如果一定要引用循環(huán)變量怎么辦藐翎?方法是再創(chuàng)建一個函數(shù),用該函數(shù)的參數(shù)綁定循環(huán)變量當前的值实幕,無論該循環(huán)變量后續(xù)如何更改吝镣,已綁定到函數(shù)參數(shù)的值不變:

function count() {
    var arr = [];
    for (var i=1; i<=3; i++) {
        arr.push((function (n) {
            return function () {
                return n * n;
            }
        })(i));
    }
    return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];

f1(); // 1
f2(); // 4
f3(); // 9

注意這里用了一個“創(chuàng)建一個匿名函數(shù)并立刻執(zhí)行”的語法:

(function(n){
    return n*n;
})(3); ===> 9

閉包中的this

  var name = "The Window";

  var object = {
    name : "My Object",

    getNameFunc : function(){
      return function(){
        return this.name;
      };

    }

  };

  alert(object.getNameFunc()());
===> The Window

在上面這段代碼中,obj.getName()()實際上是在全局作用域中調(diào)用了匿名函數(shù)昆庇,this指向了window末贾。這里要理解函數(shù)名與函數(shù)功能是分割開的,不要認為函數(shù)在哪里整吆,其內(nèi)部的this就指向哪里拱撵。window才是匿名函數(shù)功能執(zhí)行的環(huán)境辉川。

如果想使this指向外部函數(shù)的執(zhí)行環(huán)境,可以這樣改寫:

  var name = "The Window";
  var object = {
    name : "My Object",
    getNameFunc : function(){
      var that = this;
      return function(){
        return that.name;
      };
    }
  };

  alert(object.getNameFunc()());
===> My Object

在閉包中拴测,arguments與this也有相同的問題员串。下面的情況也要注意:

  var name = "The Window";
  var object = {
    name : "My Object",
    getName : function(){
      retuen this.name;
    }
  };

object.getName() ===> My Object

(Object.getName = Object.getName)()  ===> The Window

obj.getName();這時getName()是在對象obj的環(huán)境中執(zhí)行的,所以this指向obj昼扛。

(obj.getName = obj.getName)賦值語句返回的是等號右邊的值寸齐,在全局作用域中返回,所以(obj.getName = obj.getName)();的this指向全局抄谐。要把函數(shù)名和函數(shù)功能分割開來渺鹦。

閉包的主要應用場景

  1. 私有變量 實現(xiàn) private的功能
'use strict';

function create_counter(initial) {
    var x = initial || 0;
    return {
        inc: function () {
            x += 1;
            return x;
        }
    }
}
var c1 = create_counter();
c1.inc(); // 1
c1.inc(); // 2
c1.inc(); // 3

var c2 = create_counter(10);
c2.inc(); // 11
c2.inc(); // 12
c2.inc(); // 13

在返回的對象中,實現(xiàn)了一個閉包蛹含,該閉包攜帶了局部變量x毅厚,并且,從外部代碼根本無法訪問到變量x浦箱。換句話說吸耿,閉包就是攜帶狀態(tài)的函數(shù),并且它的狀態(tài)可以完全對外隱藏起來

  1. 把多參數(shù)的函數(shù)變成單參數(shù)的函數(shù)
'use strict';

function make_pow(n) {
    return function (x) {
        return Math.pow(x, n);
    }
}

// 創(chuàng)建兩個新函數(shù):
var pow2 = make_pow(2);
var pow3 = make_pow(3);

console.log(pow2(5)); // 25
console.log(pow3(7)); // 343

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末酷窥,一起剝皮案震驚了整個濱河市咽安,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蓬推,老刑警劉巖妆棒,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異沸伏,居然都是意外死亡糕珊,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門毅糟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來红选,“玉大人,你說我怎么就攤上這事姆另±撸” “怎么了?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵蜕青,是天一觀的道長苟蹈。 經(jīng)常有香客問我,道長右核,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任渺绒,我火速辦了婚禮贺喝,結果婚禮上菱鸥,老公的妹妹穿的比我還像新娘。我一直安慰自己躏鱼,他們只是感情好氮采,可當我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著染苛,像睡著了一般鹊漠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上茶行,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天躯概,我揣著相機與錄音,去河邊找鬼畔师。 笑死娶靡,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的看锉。 我是一名探鬼主播姿锭,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼伯铣!你這毒婦竟也來了呻此?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤腔寡,失蹤者是張志新(化名)和其女友劉穎趾诗,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蹬蚁,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡恃泪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了犀斋。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贝乎。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖叽粹,靈堂內(nèi)的尸體忽然破棺而出览效,到底是詐尸還是另有隱情,我是刑警寧澤虫几,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布锤灿,位于F島的核電站,受9級特大地震影響辆脸,放射性物質發(fā)生泄漏但校。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一啡氢、第九天 我趴在偏房一處隱蔽的房頂上張望状囱。 院中可真熱鬧术裸,春花似錦、人聲如沸亭枷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽叨粘。三九已至猾编,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間升敲,已是汗流浹背答倡。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留冻晤,地道東北人苇羡。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像鼻弧,于是被迫代替她去往敵國和親设江。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容