摘要:在函數(shù)內(nèi)部的變量稱(chēng)之為局部變量,它可以在函數(shù)內(nèi)部讀取,在函數(shù)外部無(wú)法正常讀取,如果想要讀取函數(shù)內(nèi)部的變量則需要用到閉包。
前端面試之閉包
閉包屬于屬于JavaScript的難點(diǎn),但是在很多高級(jí)應(yīng)用都需要用到,也是前端面試中經(jīng)常會(huì)考到的點(diǎn)。
作用域談到閉包首先必須了解作用域,ES5中,JavaScript的作用域只有兩種,一種是全局作用域,變量在整個(gè)程序中一直存在,所有地方都可以讀?。涣硪环N是函數(shù)作用域,變量只在函數(shù)內(nèi)部存在。
JavaScript中變量分為兩種:全局變量,局部變量。
全局變量在程序中的任何一個(gè)位置都可以調(diào)用,及賦值。
在函數(shù)內(nèi)部的變量稱(chēng)之為局部變量,它可以在函數(shù)內(nèi)部讀取,在函數(shù)外部無(wú)法正常讀取,如果想要讀取函數(shù)內(nèi)部的變量則需要用到閉包。父函數(shù)內(nèi)部定義了子函數(shù),子函數(shù)可以引用父函數(shù)作用域中的變量。
在網(wǎng)上找了一個(gè)圖比較好的解釋了變量與作用域之間的微妙關(guān)系。
必須注意的一點(diǎn)的是函數(shù)本身的作用域,是定義時(shí)的作用域,這里與this的指向不同。
var a = 1; var f = function(){ console.log(a); } function f2(){ var a = 2; f(); } f2() // 1
函數(shù)f在全局作用域下定義的,雖然在f2中被引用,但是a仍然是全局作用域下的a。
javascript 中的垃圾收集機(jī)制在談到閉包之前,還有一個(gè)垃圾回收機(jī)制需要了解。
JavaScript的內(nèi)存生命周期:
分配所需要的內(nèi)存
使用分配到的內(nèi)存(讀、寫(xiě))
不需要時(shí)將其釋放
垃圾回收機(jī)制的原理其實(shí)很簡(jiǎn)單:確定變量中哪些還在繼續(xù)使用的,哪些已經(jīng)不用的,然后垃圾收集器每隔固定的時(shí)間就會(huì)清理一下,釋放內(nèi)存。
局部變量在程序執(zhí)行過(guò)程中,會(huì)為局部變量分配相應(yīng)的空間,然后在函數(shù)中使用這些變量,如果函數(shù)運(yùn)行結(jié)束了,而且在函數(shù)之外沒(méi)有仔引用這個(gè)變量了,局部變量就沒(méi)有存在的價(jià)值了,因此會(huì)被垃圾回收機(jī)制回收。在這種情況下,很容易辨別,但是并非所有情況下都這么容易。比如說(shuō)全局變量。在現(xiàn)代瀏覽器中,通常使用標(biāo)記清除策略來(lái)辨別及實(shí)現(xiàn)垃圾回收(還有一種叫引用計(jì)數(shù),即當(dāng)變量的引用次數(shù)為零的時(shí)候,就表示不再使用,這里有個(gè)循環(huán)計(jì)數(shù)的bug,現(xiàn)代瀏覽器已經(jīng)不再使用它)。
標(biāo)記清除
標(biāo)記清除會(huì)給內(nèi)存中所有的變量都加上標(biāo)記,然后去掉環(huán)境中的變量以及不在環(huán)境中但是被環(huán)境中變量引用的變量(閉包)的標(biāo)記。剩下的被標(biāo)記的就是等到被刪除的變量,原因是環(huán)境中的變量已經(jīng)無(wú)法訪問(wèn)到這些變量了。最后垃圾回收器會(huì)完成內(nèi)存清理,銷(xiāo)毀那些被標(biāo)記的值釋放內(nèi)存空間。
閉包首先拋出一條閉包的定義:閉包是指這樣的作用域,它包含有一個(gè)函數(shù),這個(gè)函數(shù)可以調(diào)用被這個(gè)作用域所封閉的變量、函數(shù)或者閉包等內(nèi)容。
由定義可以看出:
閉包指的是一個(gè)函數(shù)作用域
閉包包含一個(gè)函數(shù),且這個(gè)函數(shù)調(diào)用了作用域里的內(nèi)容
滿(mǎn)足這兩點(diǎn),都可以叫做閉包。
正常情況下,一個(gè)函數(shù)執(zhí)行完,且沒(méi)有在任何地方被調(diào)用,這個(gè)函數(shù)將會(huì)被垃圾回收機(jī)制銷(xiāo)毀。
舉個(gè)例子:
var obj = function () { var a = ""; return { set: function (val) { a = val; }, get: function () { return a; } } }; var b = obj(); b.set("new val"); b.get();
obj這個(gè)函數(shù)在執(zhí)行完之后理論上 函數(shù)體內(nèi)的東西都應(yīng)該被回收掉。但它執(zhí)行后的返回值 b 具有set和get方法。這兩個(gè)方法里對(duì)a保持了引用,所以obj執(zhí)行過(guò)程中產(chǎn)生的a就不會(huì)銷(xiāo)毀。直到b先被回收,這個(gè)a才會(huì)回收。
閉包利用的就是以上原理,以下是一個(gè)閉包的例子:
function f1() { var a = 1; function f2() { console.log(a); } return f2; } var a = 2; var f = f1(); f() // 1
f執(zhí)行完之后,其實(shí)f指向的f2這個(gè)函數(shù)在f1這個(gè)函數(shù)的作用域的引用,也就是說(shuō),執(zhí)行f,相當(dāng)于在f1函數(shù)的作用域這個(gè)環(huán)境下,執(zhí)行f2。原因是f2是在f1中定義的,而且在這個(gè)例子中,a的值不會(huì)受外界影響。
閉包的特點(diǎn)1.閉包內(nèi)的變量不會(huì)影響到全局變量,也不會(huì)被全局變量所影響
2.閉包中被函數(shù)引用的局部變量不會(huì)被垃圾回收機(jī)制回收
3.可以創(chuàng)建私有變量和私有函數(shù)
4.可以把需要公開(kāi)的變量和方法綁定在window上放出來(lái)
(function() { // 私有變量 var age = 20; var name = "Tom"; // 私有方法 function getName() { return `your name is ` + name; } // 共有方法 function getAge() { return age; } // 將引用保存在外部執(zhí)行環(huán)境的變量中,形成閉包,防止該執(zhí)行環(huán)境被垃圾回收 window.getAge = getAge; })();閉包在ES6中的運(yùn)用
ES6引入了塊級(jí)作用域,主要是let命令以及const命令。他們的特點(diǎn)是不存在變量提升,不可以重復(fù)聲明,只在區(qū)塊中有效,存在暫時(shí)性死區(qū)。
借一個(gè)阮一峰老師的暫時(shí)性死區(qū)的例子:
var tmp = 123; if (true) { tmp = "abc"; // ReferenceError let tmp; }
在條件語(yǔ)句的區(qū)塊中,雖然tmp在賦值后再用let命令聲明,但是let命令已經(jīng)生效,不歸var所管了。
首先拋磚引玉,來(lái)一個(gè)關(guān)于ES5經(jīng)典的例子:
var test = function () { var arr = []; for(var i = 0; i < 5; i++){ arr.push(function () { return i*i; }) } return arr; } var test1 = test(); console.log(test1[0]()); console.log(test1[1]()); console.log(test1[2]());
這個(gè)例子就不用多講了,最后輸出的值都是25。要注意的有兩點(diǎn),一個(gè)是i的變量提升,一個(gè)是i++,i++實(shí)際作用位置為當(dāng)前循環(huán)內(nèi)容結(jié)束,下一個(gè)循環(huán)之前。i++的意思是當(dāng)前語(yǔ)句結(jié)束后,i加1。當(dāng)我們打印i的值的時(shí)候,i的循環(huán)已經(jīng)執(zhí)行完了,i已經(jīng)變成5了。
當(dāng)我們用ES6的時(shí)候,情況就不一樣了。
var test = function () { const arr = []; for(let i = 0; i < 5; i++){ arr.push(function () { return i*i; }) } return arr; } var test1 = test(); console.log(test1[0]()); console.log(test1[1]()); console.log(test1[2]());
因?yàn)槭褂胠et,使得for循環(huán)為塊級(jí)作用域,let i=0在這個(gè)塊級(jí)作用域中,而不是在函數(shù)作用域中。每次循環(huán)都會(huì)創(chuàng)建一個(gè)新的塊級(jí)作用域,i值互相獨(dú)立不受影響。所以最后打印的結(jié)果是:0,1,4.
閉包實(shí)現(xiàn)一個(gè)計(jì)數(shù)器var counter = function(){ var count = 1; return function(){ return count++; } }
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/83241.html
摘要:閉包面試題解由于作用域鏈機(jī)制的影響,閉包只能取得內(nèi)部函數(shù)的最后一個(gè)值,這引起的一個(gè)副作用就是如果內(nèi)部函數(shù)在一個(gè)循環(huán)中,那么變量的值始終為最后一個(gè)值。 (關(guān)注福利,關(guān)注本公眾號(hào)回復(fù)[資料]領(lǐng)取優(yōu)質(zhì)前端視頻,包括Vue、React、Node源碼和實(shí)戰(zhàn)、面試指導(dǎo)) 本周正式開(kāi)始前端進(jìn)階的第二期,本周的主題是作用域閉包,今天是第8天。 本計(jì)劃一共28期,每期重點(diǎn)攻克一個(gè)面試重難點(diǎn),如果你還不了...
摘要:使用上一篇文章的例子來(lái)說(shuō)明下自由變量進(jìn)階期深入淺出圖解作用域鏈和閉包訪問(wèn)外部的今天是今天是其中既不是參數(shù),也不是局部變量,所以是自由變量。 (關(guān)注福利,關(guān)注本公眾號(hào)回復(fù)[資料]領(lǐng)取優(yōu)質(zhì)前端視頻,包括Vue、React、Node源碼和實(shí)戰(zhàn)、面試指導(dǎo)) 本周正式開(kāi)始前端進(jìn)階的第二期,本周的主題是作用域閉包,今天是第7天。 本計(jì)劃一共28期,每期重點(diǎn)攻克一個(gè)面試重難點(diǎn),如果你還不了解本進(jìn)階計(jì)...
摘要:今天同學(xué)去面試,做了兩道面試題全部做錯(cuò)了,發(fā)過(guò)來(lái)給道典型的面試題前端掘金在界中,開(kāi)發(fā)人員的需求量一直居高不下。 排序算法 -- JavaScript 標(biāo)準(zhǔn)參考教程(alpha) - 前端 - 掘金來(lái)自《JavaScript 標(biāo)準(zhǔn)參考教程(alpha)》,by 阮一峰 目錄 冒泡排序 簡(jiǎn)介 算法實(shí)現(xiàn) 選擇排序 簡(jiǎn)介 算法實(shí)現(xiàn) ... 圖例詳解那道 setTimeout 與循環(huán)閉包的經(jīng)典面...
摘要:最近遇到的前端面試題更新版前端掘金個(gè)人博客已上線,歡迎前去訪問(wèn)評(píng)論無(wú)媛無(wú)故的個(gè)人博客以下內(nèi)容非本人原創(chuàng),是整理后覺(jué)得更容易理解的版本,歡迎補(bǔ)充。 一道面試題引發(fā)的對(duì) javascript 類(lèi)型轉(zhuǎn)換的思考 - 前端 - 掘金 最近群里有人發(fā)了下面這題:實(shí)現(xiàn)一個(gè)函數(shù),運(yùn)算結(jié)果可以滿(mǎn)足如下預(yù)期結(jié)果: ... 收集 JavaScript 各種疑難雜癥的問(wèn)題集錦 - 前端 - 掘金 從原博客遷移...
摘要:最近遇到的前端面試題更新版前端掘金個(gè)人博客已上線,歡迎前去訪問(wèn)評(píng)論無(wú)媛無(wú)故的個(gè)人博客以下內(nèi)容非本人原創(chuàng),是整理后覺(jué)得更容易理解的版本,歡迎補(bǔ)充。 一道面試題引發(fā)的對(duì) javascript 類(lèi)型轉(zhuǎn)換的思考 - 前端 - 掘金 最近群里有人發(fā)了下面這題:實(shí)現(xiàn)一個(gè)函數(shù),運(yùn)算結(jié)果可以滿(mǎn)足如下預(yù)期結(jié)果: ... 收集 JavaScript 各種疑難雜癥的問(wèn)題集錦 - 前端 - 掘金 從原博客遷移...
閱讀 2516·2021-10-11 10:57
閱讀 1360·2021-10-09 09:59
閱讀 2080·2019-08-30 15:53
閱讀 3279·2019-08-30 15:53
閱讀 1088·2019-08-30 15:45
閱讀 828·2019-08-30 15:44
閱讀 3518·2019-08-30 14:24
閱讀 1024·2019-08-30 14:21