搜尋
首頁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
C和JavaScript:連接解釋C和JavaScript:連接解釋Apr 23, 2025 am 12:07 AM

C 和JavaScript通過WebAssembly實現互操作性。 1)C 代碼編譯成WebAssembly模塊,引入到JavaScript環境中,增強計算能力。 2)在遊戲開發中,C 處理物理引擎和圖形渲染,JavaScript負責遊戲邏輯和用戶界面。

從網站到應用程序:JavaScript的不同應用從網站到應用程序:JavaScript的不同應用Apr 22, 2025 am 12:02 AM

JavaScript在網站、移動應用、桌面應用和服務器端編程中均有廣泛應用。 1)在網站開發中,JavaScript與HTML、CSS一起操作DOM,實現動態效果,並支持如jQuery、React等框架。 2)通過ReactNative和Ionic,JavaScript用於開發跨平台移動應用。 3)Electron框架使JavaScript能構建桌面應用。 4)Node.js讓JavaScript在服務器端運行,支持高並發請求。

Python vs. JavaScript:比較用例和應用程序Python vs. JavaScript:比較用例和應用程序Apr 21, 2025 am 12:01 AM

Python更適合數據科學和自動化,JavaScript更適合前端和全棧開發。 1.Python在數據科學和機器學習中表現出色,使用NumPy、Pandas等庫進行數據處理和建模。 2.Python在自動化和腳本編寫方面簡潔高效。 3.JavaScript在前端開發中不可或缺,用於構建動態網頁和單頁面應用。 4.JavaScript通過Node.js在後端開發中發揮作用,支持全棧開發。

C/C在JavaScript口譯員和編譯器中的作用C/C在JavaScript口譯員和編譯器中的作用Apr 20, 2025 am 12:01 AM

C和C 在JavaScript引擎中扮演了至关重要的角色,主要用于实现解释器和JIT编译器。1)C 用于解析JavaScript源码并生成抽象语法树。2)C 负责生成和执行字节码。3)C 实现JIT编译器,在运行时优化和编译热点代码,显著提高JavaScript的执行效率。

JavaScript在行動中:現實世界中的示例和項目JavaScript在行動中:現實世界中的示例和項目Apr 19, 2025 am 12:13 AM

JavaScript在現實世界中的應用包括前端和後端開發。 1)通過構建TODO列表應用展示前端應用,涉及DOM操作和事件處理。 2)通過Node.js和Express構建RESTfulAPI展示後端應用。

JavaScript和Web:核心功能和用例JavaScript和Web:核心功能和用例Apr 18, 2025 am 12:19 AM

JavaScript在Web開發中的主要用途包括客戶端交互、表單驗證和異步通信。 1)通過DOM操作實現動態內容更新和用戶交互;2)在用戶提交數據前進行客戶端驗證,提高用戶體驗;3)通過AJAX技術實現與服務器的無刷新通信。

了解JavaScript引擎:實施詳細信息了解JavaScript引擎:實施詳細信息Apr 17, 2025 am 12:05 AM

理解JavaScript引擎內部工作原理對開發者重要,因為它能幫助編寫更高效的代碼並理解性能瓶頸和優化策略。 1)引擎的工作流程包括解析、編譯和執行三個階段;2)執行過程中,引擎會進行動態優化,如內聯緩存和隱藏類;3)最佳實踐包括避免全局變量、優化循環、使用const和let,以及避免過度使用閉包。

Python vs. JavaScript:學習曲線和易用性Python vs. JavaScript:學習曲線和易用性Apr 16, 2025 am 12:12 AM

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

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

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

熱工具

SublimeText3 Mac版

SublimeText3 Mac版

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

Safe Exam Browser

Safe Exam Browser

Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器

EditPlus 中文破解版

EditPlus 中文破解版

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

SecLists

SecLists

SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。