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

在這裏插入圖片描述

做者 | Jeskson前端

來源 | 達達前端小酒館程序員

1

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

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);
undefined
VM1036:2 0
VM1036:2 0
VM1036:2 0

VM1036:2 undefined
VM1036:2 0
VM1036:2 1
VM1036:2 2

VM1036:2 undefined
VM1036:2 0
VM1036:2 1
VM1036:2 1
{fun: ƒ}
//答案:
//undefined,0,0,0
//undefined,0,1,2
//undefined,0,1,1

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

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

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

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

// 獲取名稱
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

函數建立的方法有哪些?

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

function funDa() {}

第二種是:建立匿名函數的表達式。設計模式

建立一個變量,這個變量的內容是一個函數,爲匿名函數瀏覽器

var funDa = function() {}

這樣這個函數就沒有了name網絡

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:3
fn @ VM1879:3
(anonymous) @ VM1879:6

var d1 = {
    fn: function(){
        console.log("dada");
    }
};
d1.fn();

VM1973:3 dada
undefined

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

在這裏插入圖片描述

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);
undefined
VM2273:2 0
VM2273:2 0
VM2273: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 {
        fun: function(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 {
        fun: function(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 {
        fun: function(2) {
            return fun(2,1);
        }
    };
}

// 1

function fun(2,1) a=2, b=1

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

// a=2
function fun(a,b) {
    console.log(b)
    return {
        fun: function(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=0
function fun(a,b) {
    console.log(b)
    return {
        fun: function(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=0
function fun(a,b) {
    console.log(b)
    return {
        fun: function(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);


undefined
VM1036:2 0
VM1036:2 0
VM1036:2 0

VM1036:2 undefined
VM1036:2 0
VM1036:2 1
VM1036:2 2

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

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

推薦閱讀

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

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

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

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

❤️ 不要忘記留下你學習的腳印 [點贊 + 收藏 + 評論]

做者Info:

【做者】:Jeskson
【原創公衆號】:達達前端小酒館。
【福利】:公衆號回覆 「資料」 送自學資料大禮包(進羣分享,想要啥就說哈,看我有沒有)!
【轉載說明】:轉載請說明出處,謝謝合做!~

大前端開發,定位前端開發技術棧博客,PHP後臺知識點,web全棧技術領域,數據結構與算法、網絡原理等通俗易懂的呈現給小夥伴。謝謝支持,承蒙厚愛!!!


若本號內容有作得不到位的地方(好比:涉及版權或其餘問題),請及時聯繫咱們進行整改便可,會在第一時間進行處理。


請點贊!由於大家的贊同/鼓勵是我寫做的最大動力!

歡迎關注達達的CSDN!

這是一個有質量,有態度的博客

前端技術棧

相關文章
相關標籤/搜索