一篇常作錯的經典JS閉包面試題



1


到底是怎麼樣的一道面試題,能讓我拿出來講說呢?下面請看代碼:
javascript


function fun(a,b) { console.log(b) return { fun: function(c) { return fun(c,a); } };}
var d = fun(0); d.fun(1); d.fun(2);d.fun(3);var d1 = fun(0).fun(1).fun(2).fun(3);var d2 = fun(0).fun(1);d2.fun(2);d2.fun(3);









function fun(a,b) { console.log(b) return { fun: function(c) { return fun(c,a); } };}var d = fun(0); d.fun(1); d.fun(2);d.fun(3);var d1 = fun(0).fun(1).fun(2).fun(3);var d2 = fun(0).fun(1); d2.fun(2);d2.fun(3);


undefinedVM1036:2 0VM1036:2 0VM1036:2 0
VM1036:2 undefinedVM1036:2 0VM1036:2 1VM1036:2 2
VM1036:2 undefinedVM1036:2 0VM1036:2 1VM1036:2 1{fun: ƒ}


//答案://undefined,0,0,0//undefined,0,1,2//undefined,0,1,1


JS函數分兩種:具名函數(命名函數)和匿名函數。
php


如何判斷兩種函數的方法呢?
css


能夠用fn.name來判斷,若是有name就是具名函數,若是沒有name就是匿名函數。前端


須要注意的是在IE瀏覽器上沒法獲取具名函數的name,會返回undefined的結果,而在谷歌瀏覽器上就能夠獲取。
java


// 獲取名稱function getFunctionName(fun){ if(fun.name !== undefined)  return fun.name;  var funName = fun.toString();  funName = funName.substr('function '.length);  funName = funName.substr(0, funName.indexOf('('));  return funName;}


2


函數建立的方法有哪些?
程序員


第一種是:聲明函數,聲明函數方法,包括函數名和函數體。web


function funDa() {}


第二種是:建立匿名函數的表達式。
面試


建立一個變量,這個變量的內容是一個函數,爲匿名函數
算法


var funDa = function({}


這樣這個函數就沒有了nametypescript


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


第三種是:建立具名函數表達式。


var funDa = function dada(){};


建立一個變量,變量賦值的內容爲一個帶有名稱的函數。具名函數表達式的函數名只能在建立函數內部使用,函數的外層只能使用funData,dada函數名只能在建立函數內部使用。




在對象內定義函數,也是屬於函數表達式。


第四種是:Function構造函數



Function("alert(1)");ƒ anonymous() {alert(1)}
Function("dada");ƒ anonymous() {dada}
new Function("alert(1)");ƒ anonymous() {alert(1)}
new Function("dada");ƒ anonymous() {dada}


Function構造函數傳一個函數字符串,返回包含這個字符串命令的函數。


第五種是:自執行函數





( function(){ alert(1);})();undefined
( function(){ alert(1);})ƒ (){ alert(1);}



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


自執行函數也是「函數表達式」。


第六種是:其餘


運用eval,setTimeout,setInterval等方法。


3



第一個fun函數是屬於標準的具名函數聲明,是新建立的函數,返回的是一個對象字面量表達式,屬性一個新的Object。


這返回,對象內部包含一個fun的屬性,屬於匿名函數表達式,這個fun屬性存放的是一個新建立匿名函數表達式,全部聲明的匿名函數都是一個新函數。則第一個fun函數和第二個fun函數不一樣,都是新建立的函數。


4


函數做用域鏈的問題



對象內部的函數表達式:


var d = { fn: function(){ console.log(fn); }};d.fn();
VM1879:3 Uncaught ReferenceError: fn is not defined at Object.fn (<anonymous>:3:21) at <anonymous>:6:3fn @ VM1879:3(anonymous) @ VM1879:6
var d1 = { fn: function(){ console.log("dada"); }};d1.fn();
VM1973:3 dadaundefined


非對象內部的函數表達式:



var da = function () { console.log(da);};da();VM2270:2 ƒ () { console.log(da);}undefined


使用var能夠訪問到存放當前函數的變量,var da,da()訪問函數的變量,在對象內部不能訪問到。


5


函數做用域鏈:


function fun(a,b) { console.log(b) return { fun: function(c) { return fun(c,a); } };}var d = fun(0); d.fun(1); d.fun(2);d.fun(3);var d1 = fun(0).fun(1).fun(2).fun(3);var d2 = fun(0).fun(1); d2.fun(2);d2.fun(3);


var d = fun(0); d.fun(1); d.fun(2);d.fun(3);


undefinedVM2273:2 0VM2273:2 0VM2273:2 0


第一個fun(0)在調用第一層fun函數,第二個fun(1)是在調用前一個fun的返回值的fun函數。即就是fun(1),fun(2),fun(3)函數獨使在調用第二層fun函數,第一次調用fun(0)時,b爲undefined,第二次調用fun(1),c爲1,a爲0。


var d = fun(0);調用的是第一層


而d.fun->fun(0).fun調用第二層


fun:function(1),return fun(1,a),fun(1,0),此時fun閉包了外層函數的a,也就是第一次調用的a=0。這樣子第一層fun函數爲fun(1,0),因此爲0。


第一次:


function fun(0,undefined) {    console.log(undefined) return {        funfunction(c) {            return fun(c,0); } };}


fun(0),b爲undefined,fun(0).fun(1),c=1,此時fun閉包外層函數的a,也就是第一次調用的a=0,即c=1,a=0,並在內部調用第一層fun函數fun(1,0),因此b=0。


function fun(a,b) { console.log(b) return {        funfunction(1) {            return fun(1,0); } };}


第三次調用fun(2)時,c爲2,仍是調用d.fun,仍是閉包了第一次調用時的a,fun(2,0)因此輸出b爲0。


function fun(a,b) { console.log(b) return { fun: function(c) { return fun(c,a); } };}var d = fun(0); d.fun(1); d.fun(2); d.fun(3);


6


var d1 = fun(0).fun(1).fun(2).fun(3);



從fun(0)調用第一層fun函數,返回值爲一個對象,第二個fun(1)調用的是第二層fun函數,後面的也是第二層fun函數。


第一層fun(0),b爲undefined,第二層.fun(1)時c爲1,c=1,a=0,內部調用第一層fun函數fun(1,0),因此b爲0。


調用你.fun(2)時,c爲2,此時當前的fun函數不是第一次執行的返回對象,而是第二次執行的返回對象,第二次執行第一層fun函數是:


fun(1,0),a=1,b=0。第三次執行fun函數,c=2,a=1


function fun(a,b) { console.log(b) return { fun: function(1) { return fun(1,0); } };}
fun(1,0),a=1,b=0。第三次執行fun函數,c=2,a=1
function fun(a,b) { console.log(b) return {        funfunction(2) {            return fun(2,1); } };}
// 1
function fun(2,1) a=2, b=1


第四次調用.fun(3)爲c爲3


// a=2function fun(a,b) { console.log(b) return {        funfunction(3) {            return fun(3,2); } };}


7


var d2 = fun(0).fun(1);d2.fun(2);d2.fun(3);



var d2=fun(0).fun(1);

// undefined, 0

此時的return(c=1,a=0),return fun(1,0),因此b爲0


d2.fun(2);

第三次調用.fun(2),c爲2

// c爲2,a=1,b=0function fun(a,b) { console.log(b) return {        funfunction(2) {            return fun(2,a); } };}
因此return fun(2,1)function fun(a=2,b=1),因此爲 1


d2.fun(3),c爲3,仍是調用的第二次的返回值,最終調用第一層的

fun(a,b)


// c爲3,a=1,b=0function fun(a,b) { console.log(b) return {        funfunction(3) {            return fun(3,a); } };}
因此return fun(3,1)function fun(a=3,b=1),因此爲 1


注意這裏的:


// c爲3,a=1,b=0這是調用這個代碼的結果
a=1,b=0


var d2 = fun(0).fun(1);


好了,這樣就知道大概的答案和講解了:


8


總的來講,你明白了!講的好辛苦,給個贊哦!求獎勵,我來了



function fun(a,b) { console.log(b) return { fun: function(c) { return fun(c,a); } };}var d = fun(0); d.fun(1); d.fun(2);d.fun(3);var d1 = fun(0).fun(1).fun(2).fun(3);var d2 = fun(0).fun(1); d2.fun(2);d2.fun(3);

undefinedVM1036:2 0VM1036:2 0VM1036:2 0
VM1036:2 undefinedVM1036:2 0VM1036:2 1VM1036:2 2
VM1036:2 undefinedVM1036:2 0VM1036:2 1VM1036:2 1{fun: ƒ}



一名喜好編程技術與專一於前端的程序員,將web前端領域、數據結構與算法、網絡原理等通俗易懂的呈現給小夥伴。分享web前端相關的技術文章、工具資源,精選課程、熱點資訊。



一、你知道多少this,new,bind,call,apply?那我告訴你


二、爲何學習JavaScript設計模式,由於它是核心


三、一篇文章把你帶入到JavaScript中的閉包與高級函數


四、大廠HR面試ES6中的深刻淺出面試題知識點


感謝閱讀,原創不易,喜歡就點個[在看] or [轉發朋友圈],這是我寫做最大的動力。

本文分享自微信公衆號 - web前端學習圈(web-xxq)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索