閉包不能吃...

爲何要寫深刻理解

筆者並非大神,只是一個在校的大三學生。開始寫深刻理解系列是爲了給js的一些重難點知識進行梳理,而不是每次面試以前都將這些知識從新理解一遍。有理解的不對的,請賜教!事不宜遲,咱們開始吧。javascript

什麼是閉包

閉包是函數!(廢話)閉包仍是一個能夠訪問函數中變量的函數。java

function who(){
  let name='clong';
  function print(){
    return name;
  }
  return print;
}
let boy=who();
let myName=boy();
console.log(myName);//'clong'

開始的定義可能會令你感受晦澀難懂,看了上面的例子咱們一塊兒來理解一下。git

分析

下面咱們來分析一下上面的栗子。github

  • 首先咱們聲明瞭一個函數,這個函數包含了一個變量name和一個print函數,執行到return print的時候進行返回。
  • 緊接着咱們在全局做用域下生命了一個變量boy,繼續向該行後面看,有個who,接着咱們遇到(),這時咱們就會從新返回到上面查找who有沒有聲明。
  • 進入who體內,咱們聲明瞭一個變量name並賦值爲clong,而後申明瞭一個print函數,接着咱們返回print,此時print函數被銷燬,而boy保存着print函數的引用。
  • 接着咱們遇到myName,基本流程與上面的boy是差很少一致的,只不過這時咱們的myName不是保存着一個函數,而是保存了name的值(注意:這裏name已經被銷燬了,不信你能夠在全局做用域下打印name的值看看!)。

有了上面的栗子,咱們來看看下面這個栗子:面試

function createCounter() {
  let counter = 0
  const myFunction = function() {
      counter = counter + 1
      return counter
    }
    return myFunction
  }
  const increment = createCounter()
  const c1 = increment()
  const c2 = increment()
  const c3 = increment()
  console.log('example increment', c1, c2, c3)//1,2,3

思考一下,答案與你認爲的同樣嗎?是否是覺得是1,1,1呢?先不要急,咱們再來看看下面這個栗子。閉包

var makeCounter = function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }  
};

var Counter1 = makeCounter();
var Counter2 = makeCounter();
console.log(Counter1===Counter2);
console.log(Counter1.value()); /* logs 0 */
Counter1.increment();
Counter1.increment();
console.log(Counter1.value()); /* logs 2 */
Counter1.decrement();
console.log(Counter1.value()); /* logs 1 */
console.log(Counter2.value()); /* logs 0 */

看看上面的栗子,你會不會疑惑?本質上都是調用的私有函數的方法,爲何Counter1和Counter2的privateCounter就會徹底不同呢?函數

筆者查看了不少資料,別人總結的要麼就是將mdn上的解釋一貼,要麼就是含糊其辭。性能

請注意兩個計數器 counter1 和 counter2 是如何維護它們各自的獨立性的。每一個閉包都是引用本身詞法做用域內的變量 privateCounter 。每次調用其中一個計數器時,經過改變這個變量的值,會改變這個閉包的詞法環境。然而在一個閉包內對變量的修改,不會影響到另一個閉包中的變量。————MDN

那麼爲何對一個閉包的變量的改變不會影響到另外一個閉包中的變量呢?我思考了好久,最後這樣解釋給本身聽:this

前一個栗子中,咱們的increment保存的是myFunction的引用和他的閉包(important:函數在建立的時候就會造成本身的做用域鏈)。瞭解過閉包的應該都有保存在內存中這個概念,那麼咱們這裏沒得操做都是直接對閉包的操做,價值做用域的執行順序(閉包=>父級=>...),每次都是從閉包中獲取,而且將修改的值保存在內存中,天然是1,2,3!code

那麼後面一個栗子呢?我先姑且解釋看看(有不對的但願大牛能夠指出),函數中的變量都是私有的(包括函數),第二個栗子返回的是一個對象,而後咱們後面的操做都是基於這個對象的屬性的操做,間接操做了內部的私有方法並獲取了內部的值。那麼,回想一下new一個對象發生了什麼?

  • 建立一個空對象
  • 將構造函數的做用域賦給新對象,即將this只想新對象
  • 講原型中的屬性添加到這個對象當中
  • 返回新對象

這個栗子不是正好跟上面的操做相似嗎?若是仍是不明白,咱們假設makeCounter是一個Array對象,裏面的private和changeBy是length和push內部實現原理,返回一個新對象,而且給了你一些接口,而這個接口正好有push,使你能夠進行push操做,且僅限於該對象的私有屬性的方式。那麼實例與實例的私有屬性或方法共有嗎?固然不!神奇的利用閉包就實現了數據的私有和封裝了

用處

通過了上面的分析,咱們大概能夠了解到閉包的一些用處了吧。

  • 訪問函數中的變量
  • 函數屬性的私有化/封裝(PS:這一塊還涉及到原型鏈,下次再寫)

劣勢

這些也是耳熟能詳了!

  • 性能問題,一直暴露在內存當中,沒法被垃圾回收機制回收,頗有可能形成內存泄漏!

場景

  • 回調函數
  • 頁面交互操做(同上!)
  • setTimeout(不也是回調函數嘛!)
  • 數據私有和封裝

tips

  • 仍然能夠訪問外部函數的中定義的變量即便外部函數被返回了
  • 閉包存儲對外部函數中變量的引用,而不是值
  • 閉包能夠實現js的數據的封裝和私有化

參考資料

MDN/JS/閉包
Understand JavaScript Closures With Ease
I never understood JavaScript closures

博客地址

相關文章
相關標籤/搜索