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