js閉包通俗解析

開場白

對於js來說,閉包可謂時重點和難點,前端面試應該是必問的題,對閉包的理解程度就可以看的出一個前端開發的功底。網上對閉包的文章比比皆是,但是對於初學者來說不是很友好。今天我就考慮一下初學者,畢竟我還算是吧,簡單通俗的講一下js閉包。

準備工作

要修煉js的閉包功夫,還需要一點其他功力作爲功底的。

  • 功底一:js的垃圾回收知識
  • 功底二:js的函數執行過程
  • 功底三:js的全局變量和局部變量

大概就明白這三點就可以理解閉包了吧。對這三點也就理解個基本 不用過深的理解比較複雜的情況,如js的預處理機制,js的堆棧內存,js的變量提升什麼的。

現在我們就簡單學習一個上面三套功法。都是之學招式不學精髓。

看了一下功底三最爲好學。一句話 函數內生成的變量叫局部變量 函數外生成的變量叫全局變量 話不嚴謹應該好理解,其實後面還有兩句,函數內可以訪問全局變量,函數外訪問不了局部變量 ;太長了不太好記。用例子來說吧。

   var a = 'AAAA';
   function fn() {
       var b = 'BBB';
       console.log(a)  //打印AAA
   }
   fn();
   console.log(b)  // b is not defined

上面例子就不解釋了。。。

再看一下js的垃圾回收機制:也是一句話 局部變量在函數執行完成之後自動回收 就拿上面的函數來說,當fn執行完成之後函數內創建的變量會自動回收

最麻煩的功底二來了,關於函數的執行過程,基礎的就是函數開始不執行,調用之後從上到下依次執行,編譯錯誤和return就結束,但是複雜的就是有函數的 有return的。舉個例子吧


   function fn() {
       var b = 'BBB';
       return function () {
          console.log("CCCC")
       }
   }
   fn();

咱們來分步執行一下 

  1. 代碼開始了走到fn(){} 這個函數,沒有執行語句跳過它。
  2. 走到fn() 開始執行fn這個函數  最終返會了一個函數。結束
  3. 函數結束 局部變量 b 被回收

再變一下型 往後加個劇本

   function fn() {
       var b = 'BBB';
       return function () {
          console.log("CCCC")
       }
   }
  var c = fn();
  c();

接上面的 返回一個函數  functio(){console.log('CCC')} 賦值給 c這個變量;(加一句:b被回收) 接着執行c() 這個函數打印出CCC(加一句 如上面return的函數內定義變量,也會被回收)

閉包開講

準備工作完成之後一個普通函數的經歷過程應該可以看的懂了吧。

那就看一個例子一步一步執行一下

/*1*/  function fn() {
/*2*/      var b = 'BBB';
/*3*/       return function () {     
/*4*/         console.log(b)
/*5*/       }
/*6*/   }
/*7*/  var c = fn();
/*8*/  c();
/*9*/  c();
/*10*/ c();

函數有點長給標一個行號 ;長函數不要着急 一步一步來。 

  1. 1-6行定義了一個函數不執行
  2. 第7行返回了一個函數 
    function () {
            console.log(b)
      } 賦值個變量c   c就是該函數了
  3. 按照上面的垃圾回收 函數內的b變量被回收
  4. 第8行執行函數c 打印b,因爲b被銷燬了 所以應該是  b is not defined 結束 9行10行就不執行了

代碼複製運行 垃圾小編 不對!  看來js處理和我們想的不一樣。

那就一起來分析一下吧? 結果顯示 打印了三個 BBB,說明程序中沒有出錯,也就說b變量沒有被銷燬,再說一下就是外面可以訪問內部函數定義的變量;這就生成了閉包。閉包最核心的也就是打破了js的垃圾回收機制,讓局部變量不被銷燬。曾看到一篇文章把閉包比喻成一個揹包,可以把函數的變量放進去,當它被返回時就一起帶走了。

那咱們再走一邊上面程序,加一句話

   /*1*/  function fn() {
   /*2*/      var b = 'BBB';
   /*3*/       return function () {
   /*4*/         b = b+'B'
   /*5*/         console.log(b)
   /*6*/       }
   /*7*/   }
   /*8*/  var c = fn();
   /*9*/  c();
   /*10*/ c();
   /*11*/ c();
  1. 1-7行定義一個函數
  2. 第8行返回了一個函數 
    function () {
            b = b+'B'
            console.log(b)
      } 賦值個變量c   c就是該函數了
  3. 因爲返回的函數 用到fn這個函數內生成的b變量。所以就打開揹包把 b='BBB',給揹走了,就成了以下函數
    var b = 'BBB'
    function () {
             b = b+'B'
             console.log(b)
           }
  4. 第九行執行c函數兩步① b 的值改爲爲'BBB'+'B'  爲BBBB ② 打印'BBBB'
  5. 第10行再執行c函數 剛纔把b的值已經改爲'BBBB'了 再加一個B 打印'BBBBB'
  6. ...

這就是一個簡單的閉包例子,也是很多筆試題閉包實現數字累計的方法。

有的同學就會說 定義一個全局變量,底下函數直接給賦值就好了。這裏只講閉包不講程序設計,爲何要避免全局變量。要實現一個功能for 0-100循環不是也可以直接寫100下。

知識梳理

  • 閉包產生的條件:函數內部函數作爲返回值,並使用函數內的局部變量
  • 閉包的表現:所用的變量不被銷燬,一直存在內存中 這就是閉包常說的 可以導致內存泄露 和 變量提升。這就是可以成爲私有屬性創建模式

應用案例

1.實現累加計數  這個例子和上面的差不多 只是有兩個return 按上面的就很好理解了

  function fn() {
       var init = 0;
       return function () {
           init = init+1
           return init;
       }
   }
   var fun = fn();
   var add1 = fun();
   var add2 = fun();
   var add3= fun();
   var add4 = fun();
   console.log(add1);
   console.log(add2);
   console.log(add3);
   console.log(add4);

2.實現函數私有屬性

1.   function book() {
2.       var page = 100;
3.       return function () {
4.           this.auther = 'wuy';
5.           this.price = 200;
6.           this._page = function () {
7.               return page
8.           }
9.       }
10.   }
11.   var books =  book();
12.   var cssbook = new books();
13.   console.log(cssbook._page());  //打印100
14.   console.log(cssbook.auther);   //打印wuy

簡單分析一下

  1. 1-10行 先定義一個函數book ,函數,
  2. 11行 函數開始運行 執行 2-9行代碼,定義一個page局部變量 返回返回值爲一個函數 3-9行 返回的函數就用到了book內定義的page 閉包形成 page 一直存在內存中。
  3. 第12行 返回的函數 3-9行 是一個構造函數用new關鍵字聲明成一個對象 賦值爲cssbook;因此函數內部的this指向cssbook
  4. 第13行 打印cssbook的_page()這個方法的返回值  就是內存中的page  100 ;這就是函數的私有屬性
  5. 第14行 打印cssbook的auther wuy

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章