摘要:然后最外層這個函數(shù)會返回一個新對象,對象里面有一個屬性,名為,而這個屬性的值是一個匿名函數(shù),它會返回。
最近看到一條有意思的閉包面試題,但是看到原文的解析,我自己覺得有點迷糊,所以自己重新做一下這條題目。
閉包面試題原題function fun(n, o) { // ① console.log(o); return { // ② fun: function(m) { // ③ return fun(m, n); // ④ } }; } // 第一個例子 var a = fun(0); // 返回undefined a.fun(1); // 返回 ? a.fun(2); // 返回 ? a.fun(3); // 返回 ? // 第二個例子 var b = fun(0) .fun(1) .fun(2) .fun(3); //undefined,?,?,? // 第三個例子 var c = fun(0).fun(1); c.fun(2); c.fun(3); //undefined,?,?,?一、關(guān)于這個函數(shù)的執(zhí)行過程
先大致說一下這個函數(shù)的執(zhí)行過程:
① 初始化一個具名函數(shù),具名函數(shù)就是有名字的函數(shù),名字叫 fun。
② 第一個 fun 具名函數(shù)執(zhí)行之后會返回一個對象字面量表達(dá)式,即返回一個新的object對象。
{ // 這是一個對象,這是對象字面量表達(dá)式創(chuàng)建對象的寫法,例如{a:11,b:22} fun: function(m) { return fun(m, n); } }
③ 返回的對象里面含有fun這個屬性,并且這個屬性里面存放的是一個新創(chuàng)建匿名函數(shù)表達(dá)式function(m) {}。
④ 在③里面創(chuàng)建的匿名函數(shù)會返回一個叫 fun 的具名函數(shù)return fun(m, n);,這里需要說明一下這個 fun 函數(shù)返回之后的執(zhí)行過程:
1. 返回 fun 函數(shù),但默認(rèn)不執(zhí)行,因為在 js 里面,函數(shù)是可以保存在變量里面的。 2. 如果想要執(zhí)行 fun 函數(shù),那么首先會在當(dāng)前作用域?qū)ふ医衒un 名字的具名函數(shù),但是因為當(dāng)前作用域里 fun 名字的函數(shù)是沒有被定義的,所以會自動往上一級查找。 2.1 注解:當(dāng)前的作用域里是一個新創(chuàng)建的對象,并且對象里面只有 fun 屬性,而沒有 fun 具名函數(shù) 2.2 注解:js 作用域鏈的問題,會導(dǎo)致他會不斷地往上級鏈查找。 3. 在當(dāng)前作用域沒找到,所以一直往上層找,直到找到了頂層的 fun函數(shù),然后執(zhí)行這個頂層的 fun 函數(shù)。 4. 然后這兩個 fun 函數(shù)會形成閉包,第二個 fun 函數(shù)會不斷引用第一個 fun 函數(shù),從而導(dǎo)致一些局部變量例如 n,o 得以保存。
所謂閉包:各種解釋都有,但都不是很接地氣,簡單的來說就是在 js 里面,有一些變量(內(nèi)存)可以被不斷的引用,導(dǎo)致了變量(內(nèi)存)沒有被釋放和回收,從而形成了一個獨立的存在,這里涉及了js 的作用域鏈和 js 回收機(jī)制,結(jié)合兩者來理解就可以了。二、第一個例子的輸出結(jié)果分析 1. var a = fun(0); // 返回 undefined
注解:
因為最外層的fun 函數(shù)fun(n, o)是有2個參數(shù)的,如果第二個參數(shù)沒有傳,那么默認(rèn)就會被轉(zhuǎn)換為 undefined,所以執(zhí)行之后輸出 undefined,因為 console.log 輸出的是o console.log(o);。
然后最外層這個 fun 函數(shù)會返回一個新對象,對象里面有一個屬性,名為 fun,而這個fun 屬性的值是一個匿名函數(shù),它會返回fun(m, n); 。
function fun(n, o) { // ① console.log(o); // 這里首先輸出了 n 的值為undefined return { // ② fun: function(m) { // ③ return fun(m, n); // ④ } }; }2. a.fun(1); // 返回 0
注解:
由于之前運行了var a = fun(0);,返回了一個對象,并且賦值給了變量a,所以 a 是可以訪問對象里面的屬性的,例如a.fun。
a.fun(1);這里意思是:
訪問 a 對象的 fun 屬性,因為a 的 fun 屬性的值保存的是一個匿名函數(shù)③,所以要使用的話需要加上()。
a.fun() 實際上調(diào)用的是 fun 屬性里面的匿名函數(shù),由于匿名函數(shù)返回的fun(m, n); 無法在當(dāng)前作用域找到(因為當(dāng)前作用域沒有這個定義這個函數(shù)),所以會往上找,找到了頂層的函數(shù)fun(n, o),這樣就會出現(xiàn)閉包的狀態(tài),頂層的fun 函數(shù)被內(nèi)層的 fun 函數(shù)引用,之前①的fun(0)的0被保存下來了,作為 n 參數(shù)的值。
a.fun(1)這里傳入了第一個參數(shù)1,所以就是 m=1,(因為③接收一個參數(shù))。
所以④的fun(m,n)就會是fun(1,0),所以輸出0
// 已經(jīng)執(zhí)行過一次var a = fun(0) function fun(n, o) { // ① console.log(o); return { // ② fun: function(m) { // ③ m=1 return fun(m, n); // ④ 不斷引用①,閉包生成,①的n 的值被保存為0 } }; }3. a.fun(2); // 返回 0
注解:
這里傳入一個參數(shù),參數(shù)的值為2,跟上面的a.fun(1);是一樣的流程執(zhí)行。
最終是 fun(2,0)執(zhí)行,那么輸出 o 就是0了
function fun(n, o) { // ① console.log(o); return { // ② fun: function(m) { // ③ return fun(m, n); // ④ } }; }4. a.fun(3); // 返回 0
跟上面雷同,所以不做解釋了。
二、第二個例子的輸出結(jié)果分析第二個例子其實是一個語句,只是進(jìn)行了鏈?zhǔn)秸{(diào)用,所以會有一些不一樣的處理。1. 第一個返回 undefined
var b = fun(0) // 返回 undefined
注解:
第一個返回 undefined 毋容置疑了,所以不說。
2. 第二個返回 0fun(0).fun(1) // 返回 0
注解:
執(zhí)行fun(0)的時候返回了一個對象,對象里面有 fun 屬性,而這個 fun 屬性的值是一個匿名函數(shù),這個匿名函數(shù)會返回一個 fun 函數(shù)。
當(dāng)執(zhí)行完fun(0)后,再鏈?zhǔn)街苯訄?zhí)行.fun(1)的時候,它是會調(diào)用前者返回的對象里的 fun 屬性,并且傳入了1作為第一個參數(shù),即m=1,并且返回的 fun 函數(shù)跟前者形成閉包,會不斷引用前者,所以 n=0 也被保存下來了。
所以最終執(zhí)行的時候是fun(m, n)即 fun(1,0),所以返回0
3. 第三個返回1fun(0).fun(1).fun(2)
注解:
執(zhí)行fun(0)的時候返回了一個對象,對象里面有 fun 屬性,而這個 fun 屬性的值是一個匿名函數(shù),這個匿名函數(shù)會返回一個 fun 函數(shù)。
當(dāng)執(zhí)行完fun(0)后,再鏈?zhǔn)街苯訄?zhí)行.fun(1)的時候,它是會調(diào)用前者返回的對象里的 fun 屬性,并且傳入了1作為第一個參數(shù),即m=1,并且返回的 fun 函數(shù)跟前者形成閉包,會不斷引用前者,所以 n=0 也被保存下來了。
當(dāng)再次鏈?zhǔn)街苯訄?zhí)行.fun(2)的時候,這里使用的閉包是.fun(1)返回的閉包,因為每次執(zhí)行 fun 函數(shù)都會返回一個新對象,而.fun(2)引用的是.fun(1),所以 n 的值被保留為1
.fun(2)返回的是fun(m, n),而這里會跟.fun(1)(即fun(1, o))形成閉包,所以1為 n 的值被保留。
需要注意的是,js 作用域鏈只要找到可以使用的,就會馬上停止向上搜索,所以.fun(2)找到.fun(1)就馬上停止向上搜索了,所以引用的是.fun(1)的值。
4. 第四個返回是2跟第三個返回類似,所以不做解釋了。
第三個例子的輸出結(jié)果分析// 這里已經(jīng)無需多說了,跟第二個例子類似。 var c = fun(0).fun(1); // 返回 undefined 和01. 第三個返回是1,第四個返回是1
c.fun(2); // 第三個返回 1 c.fun(3); // 第四個返回 1
注解:
基于第一個返回和第二個返回,n 已經(jīng)被賦值為1了。
然后這里雖然多次執(zhí)行了 fun 函數(shù),但是因為沒有再次形成閉包,n 的值沒有再次被改變,所以一直保持著1.
為了避免原文被吃掉,所以我這里保留了截圖,并且加了一篇解釋 js 閉包還不錯的文章作為參考使用。
大部分人都會做錯的經(jīng)典JS閉包面試題.pdf
JavaScript 的閉包原理與詳解 - CSDN博客.pdf
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/108345.html
摘要:函數(shù)柯里化在函數(shù)式編程中,函數(shù)是一等公民。函數(shù)柯里化的主要作用和特點就是參數(shù)復(fù)用提前返回和延遲執(zhí)行。可能在實際應(yīng)用場景中,很少使用函數(shù)柯里化的解決方案,但是了解認(rèn)識函數(shù)柯里化對自身的提升還是有幫助的。 最近在整理面試資源的時候,發(fā)現(xiàn)一道有意思的題目,所以就記錄下來。 題目 如何實現(xiàn) multi(2)(3)(4)=24? 首先來分析下這道題,實現(xiàn)一個 multi 函數(shù)并依次傳入?yún)?shù)執(zhí)行,...
摘要:忍者秘籍一書中,對于柯里化的定義如下在一個函數(shù)中首先填充幾個參數(shù)然后再返回一個新函數(shù)的技術(shù)稱為柯里化。回到我們的題目本身,其實根據(jù)測試用例我們可以發(fā)現(xiàn),函數(shù)的要求就是接受單一函數(shù),例如但是與柯里化不同之處在于,柯里化返回的一個新函數(shù)。 歡迎大家再一次來到我的文章專欄:從面試題中我們能學(xué)到什么,各位同行小伙伴是否已經(jīng)開始了悠閑的春節(jié)假期呢?在這里提前祝大家雞年大吉吧~哈哈,之前有人說...
摘要:閉包有多重前端知識點大百科全書前端掘金,,技巧使你的更加專業(yè)前端掘金一個幫你提升技巧的收藏集。 Vue全家桶實現(xiàn)還原豆瓣電影wap版 - 掘金用vue全家桶仿寫豆瓣電影wap版。 最近在公司項目中嘗試使用vue,但奈何自己初學(xué)水平有限,上了vue沒有上vuex,開發(fā)過程特別難受。 于是玩一玩本項目,算是對相關(guān)技術(shù)更加熟悉了。 原計劃仿寫完所有頁面,礙于豆瓣的接口API有限,實現(xiàn)頁面也有...
摘要:函數(shù)是這樣子聲明的使用了系統(tǒng)自己的構(gòu)造函數(shù)來聲明,第一個參數(shù)是,函數(shù)體內(nèi)又。構(gòu)造函數(shù)調(diào)用情況正常方式調(diào)用無窮無盡當(dāng)然,里還歸納了幾項比較簡單,我就不再翻譯了。 上一篇從一道面試題,到我可能看了假源碼中,由淺入深介紹了關(guān)于一篇經(jīng)典面試題的解法。最后在皆大歡喜的結(jié)尾中,突生變化,懸念又起。這一篇,就是為了解開這個懸念。 如果你還沒有看過前傳,可以參看前情回顧: 回顧1. 題目是模擬實現(xiàn)ES...
摘要:返回的綁定函數(shù)也能使用操作符創(chuàng)建對象這種行為就像把原函數(shù)當(dāng)成構(gòu)造器。同時,將第一個參數(shù)以外的其他參數(shù),作為提供給原函數(shù)的預(yù)設(shè)參數(shù),這也是基本的顆粒化基礎(chǔ)。 今天想談?wù)勔坏狼岸嗣嬖囶},我做面試官的時候經(jīng)常喜歡用它來考察面試者的基礎(chǔ)是否扎實,以及邏輯、思維能力和臨場表現(xiàn),題目是:模擬實現(xiàn)ES5中原生bind函數(shù)。也許這道題目已經(jīng)不再新鮮,部分讀者也會有思路來解答。社區(qū)上關(guān)于原生bind的研...
閱讀 1325·2023-04-26 00:10
閱讀 2428·2021-09-22 15:38
閱讀 3745·2021-09-22 15:13
閱讀 3503·2019-08-30 13:11
閱讀 646·2019-08-30 11:01
閱讀 3028·2019-08-29 14:20
閱讀 3208·2019-08-29 13:27
閱讀 1726·2019-08-29 11:33