搜尋
首頁web前端js教程一不小心就做錯的JS閉包面試題_javascript技巧

由工作中演變而來的面試題

這是一個我工作當中的遇到的一個問題,似乎很有趣,就當做了一道題去面試,發現幾乎沒人能全部答對並說出原因,遂拿出來聊一聊吧。

先看題目代碼:

function fun(n,o) {
 console.log(o)
 return {
  fun:function(m){
   return fun(m,n);
  }
 };
}
var a = fun(0); a.fun(1); a.fun(2); a.fun(3);//undefined,?,?,?
var b = fun(0).fun(1).fun(2).fun(3);//undefined,?,?,?
var c = fun(0).fun(1); c.fun(2); c.fun(3);//undefined,?,?,?
//问:三行a,b,c的输出分别是什么?


這是一道非常典型的JS閉包問題。其中嵌套了三層fun函數,搞清楚每層fun的函數是那個fun函數特別重要。

可以先在紙上或其他地方寫下你認為的結果,然後展開看看正確答案是什麼?

答案

//a: undefined,0,0,0
//b: undefined,0,1,2
//c: undefined,0,1,1

都答對了麼?如果都答對了恭喜你在js閉包問題當中幾乎沒什麼可以難住你了;如果沒有答案,繼續往下分析。

JS中有幾個函數

首先,在此之前需要了解的是,在JS中函數可以分為兩種,具名函數(命名函數)和匿名函數。

區分這兩個函數的方法很簡單,可以用輸出 fn.name 來判斷,有name的就是具名函數,沒有name的就是匿名函數

注意:在低版本IE上無法取得具名函數的name,會回傳undefined,建議在火狐或是Google瀏覽器上測試

或採用相容IE的取得函數name方法來取得函數名稱:

/**
  * 获取指定函数的函数名称(用于兼容IE)
  * @param {Function} fun 任意函数
  */
function getFunctionName(fun) {
  if (fun.name !== undefined)
    return fun.name;
  var ret = fun.toString();
  ret = ret.substr('function '.length);
  ret = ret.substr(0, ret.indexOf('('));
  return ret;
}

遂用上述函數檢定是否為匿名函數:

可以得知變數fn1是具名函數,fn2是匿名函數

建立函數的幾種方式

說完函數的類型,也需要了解JS中建立函數都有幾種建立方法。

1、宣告函數

最普通最標準的宣告函數方法,包括函數名稱及函數體。

function fn1(){} 

2、建立匿名函數表達式

建立一個變量,這個變數的內容為一個函數

var fn1=function (){}
注意採用此方法所建立的函數為匿名函數,即沒有函數name

var fn1=function (){};
getFunctionName(fn1).length;//0 

3、建立具名函數表達式

建立一個變量,內容為一個帶有名稱的函數

var fn1=function xxcanghai(){};
注意:具名函數表達式的函數名稱只能在建立函數內部使用

即採用此方法所建立的函數在函數外層只能使用fn1不能使用xxcanghai的函數名。 xxcanghai的命名只能在建立的函數內部使用

檢定:

var fn1=function xxcanghai(){
  console.log("in:fn1<",typeof fn1,">xxcanghai:<",typeof xxcanghai,">");
};
console.log("out:fn1<",typeof fn1,">xxcanghai:<",typeof xxcanghai,">");
fn1();
//out:fn1< function >xxcanghai:< undefined >
//in:fn1< function >xxcanghai:< function >

可以看到在函數外部(out)無法使用xxcanghai的函數名,為undefined。

注意:在物件內定義函數如var o={ fn : function (){…} },也屬於函數表達式 

4、Function建構子

可以給 Function 建構子傳一個函數字串,傳回包含這個字串指令的函數,此種方法所建立的是匿名函數。

5、自執行函數

(function(){alert(1);})();
(function fn1(){alert(1);})();

自執行函數屬於上述的“函數表達式”,規則相同

 6、其他建立函數的方法

當然還有其他建立函數或執行函數的方法,這裡不再多說,例如採用 eval , setTimeout , setInterval 等非常用方法,這裡不做過多介紹,屬於非標準方法,這裡不做過多展開

三個fun函數的關係是什麼?

說完函數型別與建立函數的方法後,就可以回歸主題,看這道面試題。

這段程式碼中出現了三個fun函數,所以第一步先搞清楚,這三個fun函數的關係,哪個函數與哪個函數時相同的。

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

先看第一个fun函数,属于标准具名函数声明,是新创建的函数,他的返回值是一个对象字面量表达式,属于一个新的object。

这个新的对象内部包含一个也叫fun的属性,通过上述介绍可得知,属于匿名函数表达式,即fun这个属性中存放的是一个新创建匿名函数表达式。

注意:所有声明的匿名函数都是一个新函数。

所以第一个fun函数与第二个fun函数不相同,均为新创建的函数。

函数作用域链的问题

再说第三个fun函数之前需要先说下,在函数表达式内部能不能访问存放当前函数的变量。

测试1:对象内部的函数表达式:

var o={
 fn:function (){
  console.log(fn);
 }
};
o.fn();//ERROR报错

测试2:非对象内部的函数表达式:

var fn=function (){
 console.log(fn);
};
fn();//function (){console.log(fn);};正确


结论:使用var或是非对象内部的函数表达式内,可以访问到存放当前函数的变量;在对象内部的不能访问到。

原因也非常简单,因为函数作用域链的问题,采用var的是在外部创建了一个fn变量,函数内部当然可以在内部寻找不到fn后向上册作用域查找fn,而在创建对象内部时,因为没有在函数作用域内创建fn,所以无法访问。

 

所以综上所述,可以得知,最内层的return出去的fun函数不是第二层fun函数,是最外层的fun函数。

所以,三个fun函数的关系也理清楚了,第一个等于第三个,他们都不等于第二个。

到底在调用哪个函数?

再看下原题,现在知道了程序中有两个fun函数(第一个和第三个相同),遂接下来的问题是搞清楚,运行时他执行的是哪个fun函数?

function fun(n,o) {
 console.log(o)
 return {
  fun:function(m){
   return fun(m,n);
  }
 };
}
var a = fun(0); a.fun(1); a.fun(2); a.fun(3);//undefined,&#63;,&#63;,&#63;
var b = fun(0).fun(1).fun(2).fun(3);//undefined,&#63;,&#63;,&#63;
var c = fun(0).fun(1); c.fun(2); c.fun(3);//undefined,&#63;,&#63;,&#63;
//问:三行a,b,c的输出分别是什么? 

1、第一行a

var a = fun(0);  a.fun(1);  a.fun(2);  a.fun(3);
可以得知,第一個fun(0)是在呼叫第一層fun函數。第二個fun(1)是在呼叫前一個fun的回傳值的fun函數,所以:

第後面幾個fun(1),fun(2),fun(3),函數都是在呼叫第二層fun函數。

遂:

第一次呼叫fun(0)時,o為undefined;

第二次呼叫fun(1)時m為1,此時fun閉包了外層函數的n,也就是第一次呼叫的n=0,即m=1,n=0,並在內部呼叫第一層fun函數fun(1,0);所以o為0;

第三次呼叫fun(2)時m為2,但仍是呼叫a.fun,所以還是閉包了第一次呼叫時的n,所以內部呼叫第一層的fun(2,0) ;所以o為0

第四次同理;

即:最終答案為undefined,0,0,0

2、第二行b

var b = fun(0).fun(1).fun(2).fun(3);//undefined,?,?,?
先從fun(0)開始看,一定是調用的第一層fun函數;而他的返回值是一個對象,所以第二個fun(1)調用的是第二層fun函數,後面幾個也是呼叫的第二層fun函數。

遂:

在第一次呼叫第一層fun(0)時,o為undefined;

第二次呼叫.fun(1)時m為1,此時fun閉包了外層函數的n,也就是第一次呼叫的n=0,即m=1,n=0,並在內部呼叫第一層fun函數fun(1,0);所以o為0;

第三次呼叫 .fun(2)時m為2,此時目前的fun函數不是第一次執行的回傳對象,而是第二次執行的回傳對象。而在第二次執行第一層fun函數時(1,0)所以n=1,o=0,返回時閉包了第二次的n,遂在第三次調用第三層fun函數時m =2,n=1,即呼叫第一層fun函數fun(2,1),所以o為1;

第四次調用.fun(3)時m為3,閉包了第三次調用的n,同理,最終調用第一層fun函數為fun(3,2);所以o為2;

即最終答案:undefined,0,1,2

 3、第三行c

var c = fun(0).fun(1);  c.fun(2);  c.fun(3);//undefined,?,?,?
根據前面兩個例子,可以得知:

fun(0)為執行第一層fun函數,.fun(1)執行的是fun(0)返回的第二層fun函數,這裡語句結束,遂c存放的是fun(1)的返回值,而不是fun(0)的回傳值,所以c中閉包的也是fun(1)第二次執行的n的值。 c.fun(2)執行的是fun(1)傳回的第二層fun函數,c.fun(3)執行的也是fun(1)傳回的第二層fun函數。

遂:

在第一次呼叫第一層fun(0)時,o為undefined;

第二次呼叫.fun(1)時m為1,此時fun閉包了外層函數的n,也就是第一次呼叫的n=0,即m=1,n=0,並在內部呼叫第一層fun函數fun(1,0);所以o為0;

第三次呼叫.fun(2)時m為2,此時fun閉包的是第二次呼叫的n=1,即m=2,n=1,並在內部呼叫第一層fun函數fun(2,1);所以o為1;

第四次.fun(3)時同理,但依然是調用的第二次的回傳值,遂最終調用第一層fun函數fun(3,1),所以o還為1

即最終答案:undefined,0,1,1

後話

這段程式碼原本是在做一個將非同步回呼改寫為同步呼叫的元件時的程式碼,發現了這個坑,對JS的閉包有了更深入的了解。

關於什麼是閉包,網路上的文章數不勝數,但理解什麼是閉包還是要在程式碼中自己去發現與領悟。

如果要我說什麼是閉包,我認為,廣義的閉包就是指一個變數在他自身作用域的被使用了,就叫發生了閉包。

大家都答對了嗎?希望讀者能透過本文對閉包現像有進一步的了解,如有其它見解或看法,歡迎指正討論。

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
Java vs JavaScript:開發人員的詳細比較Java vs JavaScript:開發人員的詳細比較May 16, 2025 am 12:01 AM

javaandjavascriptaredistinctlanguages:javaisusedforenterpriseandmobileapps,while javascriptifforInteractiveWebpages.1)JavaisComcompoppored,statieldinglationallyTypted,statilly tater astrunsonjvm.2)

JavaScript數據類型:瀏覽器和nodejs之間是否有區別?JavaScript數據類型:瀏覽器和nodejs之間是否有區別?May 14, 2025 am 12:15 AM

JavaScript核心數據類型在瀏覽器和Node.js中一致,但處理方式和額外類型有所不同。 1)全局對像在瀏覽器中為window,在Node.js中為global。 2)Node.js獨有Buffer對象,用於處理二進制數據。 3)性能和時間處理在兩者間也有差異,需根據環境調整代碼。

JavaScript評論:使用//和 / * * / * / * /JavaScript評論:使用//和 / * * / * / * /May 13, 2025 pm 03:49 PM

JavaScriptusestwotypesofcomments:single-line(//)andmulti-line(//).1)Use//forquicknotesorsingle-lineexplanations.2)Use//forlongerexplanationsorcommentingoutblocksofcode.Commentsshouldexplainthe'why',notthe'what',andbeplacedabovetherelevantcodeforclari

Python vs. JavaScript:開發人員的比較分析Python vs. JavaScript:開發人員的比較分析May 09, 2025 am 12:22 AM

Python和JavaScript的主要區別在於類型系統和應用場景。 1.Python使用動態類型,適合科學計算和數據分析。 2.JavaScript採用弱類型,廣泛用於前端和全棧開發。兩者在異步編程和性能優化上各有優勢,選擇時應根據項目需求決定。

Python vs. JavaScript:選擇合適的工具Python vs. JavaScript:選擇合適的工具May 08, 2025 am 12:10 AM

選擇Python還是JavaScript取決於項目類型:1)數據科學和自動化任務選擇Python;2)前端和全棧開發選擇JavaScript。 Python因其在數據處理和自動化方面的強大庫而備受青睞,而JavaScript則因其在網頁交互和全棧開發中的優勢而不可或缺。

Python和JavaScript:了解每個的優勢Python和JavaScript:了解每個的優勢May 06, 2025 am 12:15 AM

Python和JavaScript各有優勢,選擇取決於項目需求和個人偏好。 1.Python易學,語法簡潔,適用於數據科學和後端開發,但執行速度較慢。 2.JavaScript在前端開發中無處不在,異步編程能力強,Node.js使其適用於全棧開發,但語法可能複雜且易出錯。

JavaScript的核心:它是在C還是C上構建的?JavaScript的核心:它是在C還是C上構建的?May 05, 2025 am 12:07 AM

javascriptisnotbuiltoncorc; sanInterpretedlanguagethatrunsonenginesoftenwritteninc.1)JavascriptwasdesignedAsignedAsalightWeight,drackendedlanguageforwebbrowsers.2)Enginesevolvedfromsimpleterterpretpretpretpretpreterterpretpretpretpretpretpretpretpretpretcompilerers,典型地,替代品。

JavaScript應用程序:從前端到後端JavaScript應用程序:從前端到後端May 04, 2025 am 12:12 AM

JavaScript可用於前端和後端開發。前端通過DOM操作增強用戶體驗,後端通過Node.js處理服務器任務。 1.前端示例:改變網頁文本內容。 2.後端示例:創建Node.js服務器。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

北端:融合系統,解釋
4 週前By尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆樹的耳語 - 如何解鎖抓鉤
4 週前By尊渡假赌尊渡假赌尊渡假赌
<🎜>掩蓋:探險33-如何獲得完美的色度催化劑
2 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

EditPlus 中文破解版

EditPlus 中文破解版

體積小,語法高亮,不支援程式碼提示功能

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

SublimeText3 英文版

SublimeText3 英文版

推薦:為Win版本,支援程式碼提示!

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用