1. 程式人生 > >關於JS中閉包的問題

關於JS中閉包的問題

一直以來,我都以為我已經懂了JavaScript中閉包的概念,直到有一次小夥伴突然問我這個概念的時候,我才發現我根本不知道該怎來麼跟他來講述這個概念。

那時候我就知道我是自我欺騙,打腫臉充胖子了。

所以,花了點時間去專門瞭解了一下,今天專門記錄一下自己所理解的閉包。

一. 概念

閉包,簡單來講,就是定義在函式內部的函式,使用閉包,可以讓你有權訪問另一個函式作用域內的變數。

所以,想要了解閉包的前提是,你首先要知道在JS中變數作用域的問題。

建立閉包的常見方式就是在函式內部去建立另一個函式:

function fun() {
    var variable = 'Hello World';
    function inner() {
        console.log(variable);
    }
    return inner;
}

var outer = fun();
outer(); // Hello World



在這個例子中,我們想在外部用到fun()中定義的variable的值,但是因為變數作用域的問題,我們不可能直接取到。

所以我們採取了變通的方法:在fun()函式內部又建立了一個函式inner(),這時fun()內部的variable對於inner()來說是可見的,既然inner()可以取到fun()中的變數,那麼我們將inner()返回,就可以用到fun()中定義的variable了。

閉包在此處,就是連結函式內部和外部的一個橋樑。

在這裡提一句:如果inner()內部存在新設定的變數,對於fun()函式來說是不可見的,此處涉及到JS中作用鏈的問題,理解作用鏈對於徹底理解閉包的問題很有幫助,可以參考

JavaScript高階程式設計(第四章)去了解一下作用鏈。

其實閉包的定義也就這麼簡單,對於那些過於抽象的定義,置之不理即可,不用強迫自己去理解那些比較晦澀難懂的專業定義,記住自己最終的目的並不是為了咬文嚼字,實用才是根本。

最後借用知乎上一個回答來形象的描述一下閉包的概念:



二. 閉包的用處

我總結的閉包主要用處:

  1. 讓外部可以讀取函式內部的變數。
  2. 可以封裝物件的私有屬性和私有方法。

第一點用處就是再說閉包概念時候所舉的例子。

下面說下第二點用處:可以封裝物件的私有屬性和私有方法。

function Worker(name) {
    var _salary;
    function setSalary(value) {
        _salary = value;
    }
    function getSalary() {
        return _salary;
    }
    
    return {
        name: name,
        setSalary; setSalary;
        getSalary: getSalary;
    }
}

var cxk = Worker('cXK');
cxk.setSalare(100);
cxk.getSalary(); // 100



在上面的程式碼中,通過閉包,_salary變成了cxk的私有變數。

三. 需要注意的地方

第一點需要注意的地方是關於使用閉包時記憶體的問題,因為閉包會攜帶包含它的函式的作用域,因此會比其他的函式佔用更多的記憶體,濫用閉包會造成網頁的效能問題,所以對於閉包,建議只在絕對必要時在考慮使用。

對於閉包中垃圾回收的詳細測試,參考js閉包測試/司徒正美

第二點需要注意的就是在建立閉包時可能會常犯的錯誤:在迴圈中的閉包建立問題。

function createArray() {
  var result = [];
  for (var i = 0; i < 10; i++) {
    result[i] = function () {
      return i;
    }
  }
  return result;
}

var arr = createArray();
arr[1](); // 10
arr[2](); // 10



可以看到,跟我們預期達到的結果不一樣,每一個位置上的函式都返回了10。

這是因為每一個result[i]上都儲存著createArray()函式的活動物件(參考JS中的作用鏈),而給result[i]進行賦值時,'function(){return i}'沒有執行,所以最後在arr[1]執行時,返回的i其實都是同一個值,即最後生成的i,值為10。

可以做出如下修改。

修改一:在閉包裡再新增一個閉包函式,同時立即執行。

function createArray() {
  var result = [];
  for (var i = 0; i < 10; i++) {
    result[i] = function (num) {
      return function () {
        return num;
      }
    }(i)
  }
  return result;
}

var arr = createArray();
arr[1]();



修改二:修改varlet

function createArray() {
  var result = [];
  for (let i = 0; i < 10; i++) {
    result[i] = function () {
      return i;
    }
  }
  return result;
}

var arr = createArray();
arr[1]();



以上就是我對閉包的比較淺顯的認知,如果有不對的地方,希望能夠指正,以免我誤人子弟,謝謝。