一道js閉包面試題的學(xué)習(xí)

最近看到一條有意思的閉包面試題,但是看到原文的解析,我自己覺得有點迷糊,所以自己重新做一下這條題目。

閉包面試題原題

function fun(n, o) { // ① 
  console.log(o);
  return { // ② 
    fun: function(m) { // ③ 
      return fun(m, n); // ④ 
    }
  };
}

// 第一個例子
var a = fun(0); // 返回undefined
a.fun(1); // 返回 ?
a.fun(2); // 返回 ?
a.fun(3); // 返回 ?

// 第二個例子
var b = fun(0)
  .fun(1)
  .fun(2)
  .fun(3); //undefined,?,?,?

// 第三個例子
var c = fun(0).fun(1);
c.fun(2);
c.fun(3); //undefined,?,?,?

一、關(guān)于這個函數(shù)的執(zhí)行過程

先大致說一下這個函數(shù)的執(zhí)行過程:

① 初始化一個具名函數(shù),具名函數(shù)就是有名字的函數(shù),名字叫 fun。

② 第一個 fun 具名函數(shù)執(zhí)行之后會返回一個對象字面量表達式,即返回一個新的object對象。

{  // 這是一個對象,這是對象字面量表達式創(chuàng)建對象的寫法,例如{a:11,b:22}
  fun: function(m) { 
    return fun(m, n); 
  }
}

③ 返回的對象里面含有fun這個屬性,并且這個屬性里面存放的是一個新創(chuàng)建匿名函數(shù)表達式function(m) {}。

④ 在③里面創(chuàng)建的匿名函數(shù)會返回一個叫 fun 的具名函數(shù)return fun(m, n);,這里需要說明一下這個 fun 函數(shù)返回之后的執(zhí)行過程:

1. 返回 fun 函數(shù),但默認不執(zhí)行,因為在 js 里面,函數(shù)是可以保存在變量里面的。

2. 如果想要執(zhí)行 fun 函數(shù),那么首先會在當前作用域?qū)ふ医衒un 名字的具名函數(shù),但是因為當前作用域里 fun 名字的函數(shù)是沒有被定義的,所以會自動往上一級查找。
    2.1 注解:當前的作用域里是一個新創(chuàng)建的對象,并且對象里面只有 fun 屬性,而沒有 fun 具名函數(shù)
    2.2 注解:js 作用域鏈的問題,會導(dǎo)致他會不斷地往上級鏈查找。

3. 在當前作用域沒找到,所以一直往上層找,直到找到了頂層的 fun函數(shù),然后執(zhí)行這個頂層的 fun 函數(shù)。

4. 然后這兩個 fun 函數(shù)會形成閉包,第二個 fun 函數(shù)會不斷引用第一個 fun 函數(shù),從而導(dǎo)致一些局部變量例如 n,o 得以保存。

所謂閉包:各種解釋都有,但都不是很接地氣,簡單的來說就是在 js 里面,有一些變量(內(nèi)存)可以被不斷的引用,導(dǎo)致了變量(內(nèi)存)沒有被釋放和回收,從而形成了一個獨立的存在,這里涉及了js 的作用域鏈和 js 回收機制,結(jié)合兩者來理解就可以了。

二、第一個例子的輸出結(jié)果分析

1. var a = fun(0); // 返回 undefined

注解:

  • 因為最外層的fun 函數(shù)fun(n, o)是有2個參數(shù)的,如果第二個參數(shù)沒有傳,那么默認就會被轉(zhuǎn)換為 undefined,所以執(zhí)行之后輸出 undefined,因為 console.log 輸出的是o console.log(o);
  • 然后最外層這個 fun 函數(shù)會返回一個新對象,對象里面有一個屬性,名為 fun,而這個fun 屬性的值是一個匿名函數(shù),它會返回fun(m, n); 。
function fun(n, o) { // ① 
  console.log(o);  // 這里首先輸出了  n 的值為undefined
  return { // ②  
    fun: function(m) { // ③ 
      return fun(m, n); // ④  
    }
  };
}

2. a.fun(1); // 返回 0

注解:

  • 由于之前運行了var a = fun(0);,返回了一個對象,并且賦值給了變量a,所以 a 是可以訪問對象里面的屬性的,例如a.fun。

  • a.fun(1);這里意思是:

    • 訪問 a 對象的 fun 屬性,因為a 的 fun 屬性的值保存的是一個匿名函數(shù)③,所以要使用的話需要加上()
    • a.fun() 實際上調(diào)用的是 fun 屬性里面的匿名函數(shù),由于匿名函數(shù)返回的fun(m, n); 無法在當前作用域找到(因為當前作用域沒有這個定義這個函數(shù)),所以會往上找,找到了頂層的函數(shù)fun(n, o),這樣就會出現(xiàn)閉包的狀態(tài),頂層的fun 函數(shù)被內(nèi)層的 fun 函數(shù)引用,之前①的fun(0)的0被保存下來了,作為 n 參數(shù)的值。
    • a.fun(1)這里傳入了第一個參數(shù)1,所以就是 m=1,(因為③接收一個參數(shù))。
    • 所以④的fun(m,n)就會是fun(1,0),所以輸出0
    // 已經(jīng)執(zhí)行過一次var a = fun(0)
    
    function fun(n, o) { // ① 
      console.log(o);
      return { // ② 
        fun: function(m) { // ③ m=1
          return fun(m, n); // ④ 不斷引用①,閉包生成,①的n 的值被保存為0
        }
      };
    }
    

3. a.fun(2); // 返回 0

注解:

  • 這里傳入一個參數(shù),參數(shù)的值為2,跟上面的a.fun(1);是一樣的流程執(zhí)行。
  • 最終是fun(2,0)執(zhí)行,那么輸出 o 就是0了
function fun(n, o) { // ① 
  console.log(o);
  return { // ② 
    fun: function(m) { // ③ 
      return fun(m, n); // ④ 
    }
  };
}

4. a.fun(3); // 返回 0

跟上面雷同,所以不做解釋了。

二、第二個例子的輸出結(jié)果分析

第二個例子其實是一個語句,只是進行了鏈式調(diào)用,所以會有一些不一樣的處理。

1. 第一個返回 undefined

var b = fun(0) // 返回 undefined

注解:

  • 第一個返回 undefined 毋容置疑了,所以不說。

2. 第二個返回 0

 fun(0).fun(1) // 返回 0

注解:

  • 執(zhí)行fun(0)的時候返回了一個對象,對象里面有 fun 屬性,而這個 fun 屬性的值是一個匿名函數(shù),這個匿名函數(shù)會返回一個 fun 函數(shù)。
  • 當執(zhí)行完fun(0)后,再鏈式直接執(zhí)行.fun(1)的時候,它是會調(diào)用前者返回的對象里的 fun 屬性,并且傳入了1作為第一個參數(shù),即m=1,并且返回的 fun 函數(shù)跟前者形成閉包,會不斷引用前者,所以 n=0 也被保存下來了。
  • 所以最終執(zhí)行的時候是fun(m, n)fun(1,0),所以返回0

3. 第三個返回1

fun(0).fun(1).fun(2)

注解:

  • 執(zhí)行fun(0)的時候返回了一個對象,對象里面有 fun 屬性,而這個 fun 屬性的值是一個匿名函數(shù),這個匿名函數(shù)會返回一個 fun 函數(shù)。
  • 當執(zhí)行完fun(0)后,再鏈式直接執(zhí)行.fun(1)的時候,它是會調(diào)用前者返回的對象里的 fun 屬性,并且傳入了1作為第一個參數(shù),即m=1,并且返回的 fun 函數(shù)跟前者形成閉包,會不斷引用前者,所以 n=0 也被保存下來了。
  • 當再次鏈式直接執(zhí)行.fun(2)的時候,這里使用的閉包是.fun(1)返回的閉包,因為每次執(zhí)行 fun 函數(shù)都會返回一個新對象,而.fun(2)引用的是.fun(1),所以 n 的值被保留為1
    • .fun(2)返回的是fun(m, n),而這里會跟.fun(1)(即fun(1, o))形成閉包,所以1為 n 的值被保留。
    • 需要注意的是,js 作用域鏈只要找到可以使用的,就會馬上停止向上搜索,所以.fun(2)找到.fun(1)就馬上停止向上搜索了,所以引用的是.fun(1)的值。

4. 第四個返回是2

跟第三個返回類似,所以不做解釋了。

第三個例子的輸出結(jié)果分析

// 這里已經(jīng)無需多說了,跟第二個例子類似。
var c = fun(0).fun(1); // 返回 undefined 和0

1. 第三個返回是1,第四個返回是1

c.fun(2); // 第三個返回 1
c.fun(3); // 第四個返回 1

注解:

  • 基于第一個返回和第二個返回,n 已經(jīng)被賦值為1了。

  • 然后這里雖然多次執(zhí)行了 fun 函數(shù),但是因為沒有再次形成閉包,n 的值沒有再次被改變,所以一直保持著1.


為了避免原文被吃掉,所以我這里保留了截圖,并且加了一篇解釋 js 閉包還不錯的文章作為參考使用。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 作者:小小滄海原文地址:http://www.cnblogs.com/xxcanghai/p/4991870.ht...
    IT程序獅閱讀 2,941評論 5 75
  • 在C語言中,五種基本數(shù)據(jù)類型存儲空間長度的排列順序是: A)char B)char=int<=float C)ch...
    夏天再來閱讀 3,999評論 0 2
  • 第2章 基本語法 2.1 概述 基本句法和變量 語句 JavaScript程序的執(zhí)行單位為行(line),也就是一...
    悟名先生閱讀 4,509評論 0 13
  • 什么是閉包 「函數(shù)」和「函數(shù)內(nèi)部能訪問到的變量」(也叫環(huán)境)的總和,就是一個閉包。 有些人說閉包就是函數(shù)套函數(shù),然...
    落花的季節(jié)閱讀 251評論 0 1
  • 2017年9月24日 星期日 天氣晴 八虛部位拍痧之三拍兩髀。 拍兩髀(大腿內(nèi)側(cè)與小腹交接處的腹股溝部位)治療一切...
    饒愛蘭閱讀 5,586評論 1 3

友情鏈接更多精彩內(nèi)容