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

一不小心就做錯的JS閉包面試題_javascript技巧

May 16, 2016 pm 03:29 PM
js 閉包 面試題

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

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

先看題目代碼:

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

熱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

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

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

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

建議:優秀JS開源人臉偵測辨識項目 建議:優秀JS開源人臉偵測辨識項目 Apr 03, 2024 am 11:55 AM

人臉偵測辨識技術已經是一個比較成熟且應用廣泛的技術。而目前最廣泛的網路應用語言非JS莫屬,在Web前端實現人臉偵測辨識相比後端的人臉辨識有優勢也有弱勢。優點包括減少網路互動、即時識別,大大縮短了使用者等待時間,提高了使用者體驗;弱勢是:受到模型大小限制,其中準確率也有限。如何在web端使用js實現人臉偵測呢?為了實現Web端人臉識別,需要熟悉相關的程式語言和技術,如JavaScript、HTML、CSS、WebRTC等。同時也需要掌握相關的電腦視覺和人工智慧技術。值得注意的是,由於Web端的計

C++ lambda 表達式中閉包的意思是什麼? C++ lambda 表達式中閉包的意思是什麼? Apr 17, 2024 pm 06:15 PM

在C++中,閉包是能夠存取外部變數的lambda表達式。若要建立閉包,請擷取lambda表達式中的外部變數。閉包提供可重複使用性、資訊隱藏和延遲求值等優點。它們在事件處理程序等實際情況中很有用,其中即使外部變數被銷毀,閉包仍然可以存取它們。

C++ Lambda 表達式如何實作閉包? C++ Lambda 表達式如何實作閉包? Jun 01, 2024 pm 05:50 PM

C++Lambda表達式支援閉包,即保存函數作用域變數並供函數存取。語法為[capture-list](parameters)->return-type{function-body}。 capture-list定義要捕獲的變量,可以使用[=]按值捕獲所有局部變量,[&]按引用捕獲所有局部變量,或[variable1,variable2,...]捕獲特定變量。 Lambda表達式只能存取捕獲的變量,但無法修改原始值。

C++ 函式中閉包的優點和缺點是什麼? C++ 函式中閉包的優點和缺點是什麼? Apr 25, 2024 pm 01:33 PM

閉包是一種巢狀函數,它能存取外層函數作用域的變量,優點包括資料封裝、狀態保持和靈活性。缺點包括記憶體消耗、效能影響和調試複雜性。此外,閉包還可以建立匿名函數,並將其作為回調或參數傳遞給其他函數。

解決閉包導致的記憶體洩漏問題 解決閉包導致的記憶體洩漏問題 Feb 18, 2024 pm 03:20 PM

標題:閉包造成的記憶體洩漏及解決方法引言:閉包是JavaScript中一個非常常見的概念,它可以讓內部函數存取外部函數的變數。然而,閉包在使用不當的情況下可能導致記憶體洩漏。本文將探討閉包所造成的記憶體洩漏問題,並提供解決方法及具體程式碼範例。一、閉包引起的記憶體洩漏問題閉包的特性是內部函數可以存取外部函數的變量,這意味著在閉包中引用的變數不會被垃圾回收。如果使用不當,

js和vue的關係 js和vue的關係 Mar 11, 2024 pm 05:21 PM

js和vue的關係:1、JS作為Web開發基石;2、Vue.js作為前端框架的崛起;3、JS與Vue的互補關係;4、JS與Vue的實踐應用。

函數指標和閉包對Golang效能的影響 函數指標和閉包對Golang效能的影響 Apr 15, 2024 am 10:36 AM

函數指針和閉包對Go性能的影響如下:函數指針:稍慢於直接調用,但可提高可讀性和可復用性。閉包:通常更慢,但可封裝資料和行為。實戰案例:函數指標可最佳化排序演算法,閉包可建立事件處理程序,但會帶來效能損失。

golang函數閉包在測試中的作用 golang函數閉包在測試中的作用 Apr 24, 2024 am 08:54 AM

Go語言函數閉包在單元測試中發揮著至關重要的作用:捕獲值:閉包可以存取外部作用域的變量,允許在巢狀函數中捕獲和重複使用測試參數。簡化測試程式碼:透過擷取值,閉包消除了對每個循環重複設定參數的需求,從而簡化了測試程式碼。提高可讀性:使用閉包可以組織測試邏輯,使測試程式碼更清晰、更易於閱讀。

See all articles