摘要:沒有清空的原因是,內(nèi)部函數(shù)返回的匿名函數(shù)的作用域鏈仍然保有對(duì)外部函數(shù)的變量的引用。在作用域鏈中,外部函數(shù)的活動(dòng)對(duì)象始終處于第二位,外部函數(shù)的外部函數(shù)的活動(dòng)對(duì)象處于第三位,直至作為作用域鏈終點(diǎn)的全局執(zhí)行環(huán)境。
前言
閉包這個(gè)概念幾乎成了JavaScript面試者必問的話題之一,可以毫不客氣地說對(duì)閉包的理解和運(yùn)用體現(xiàn)了一名js工程師的功底。那么閉包到底是什么,它又能帶來什么特別的作用?網(wǎng)上有很多文章和資料都講述了這個(gè)東西,但是大多解釋得比較含糊,涉及閉包底層過程卻一筆帶過,對(duì)于初學(xué)者的理解十分不友好。在這里,我想講述清楚閉包的來龍去脈,加深大家對(duì)此理解,如有講述不合理的地方,歡迎指出并交流。
例子假設(shè)我們有這樣一個(gè)需求,判斷某個(gè)對(duì)象是否為指定類型,比如判斷是否為函數(shù):
function isFunction(obj){ return (typeof obj === "function"); }
如果業(yè)務(wù)功能只需要這一種類型判斷,這么寫當(dāng)然沒有問題,但是如果業(yè)務(wù)邏輯還需要有是否為字符串類型、是否為數(shù)組類型等判斷時(shí)該怎么辦?使用switch來對(duì)傳參進(jìn)行判斷?
function isType(obj,type) { switch (type) { case "string": return (typeof obj === "string") case "array": return (typeof obj === "array") case "function": return (typeof obj === "function") default: break; } }
這樣寫似乎也還不錯(cuò),但是如果用閉包特性來寫,整體的代碼就會(huì)優(yōu)雅很多:
function isType(type){ return function(obj){ return Object.prototype.toString.call(obj) == "[object "+ type + "]" } } //定義一個(gè)判斷是否為函數(shù)類型的函數(shù) var isFunction = isType("Function"); var isString = isType("String"); //測(cè)試 var name = "Tom"; isString(name)//true
先把Object.prototype.toString與typeof的問題放一邊,這種書寫方式是否比上一個(gè)switch的方式更為清楚且易擴(kuò)展?(觀眾老爺:清楚個(gè)毛啊,明明更復(fù)雜了好吧!)稍安勿躁,下面我就解釋:
1、Object.prototype.toString與typeof都可以對(duì)變量進(jìn)行類型判斷,不同之處在于后者對(duì)引用類型的變量判斷都會(huì)返回"object",因此很難確定返回的值是不是函數(shù)。 而前者更為嚴(yán)謹(jǐn),在任何值上調(diào)用Object.toStrng()會(huì)返回一個(gè)[object NativeConstructorName]格式的字符串。
2、再來說說這里的閉包特性,isType函數(shù)的作用是返回一個(gè)用于定制類型判斷的匿名函數(shù)。當(dāng)我們調(diào)用isType("String")時(shí),得到的是一個(gè)這樣的函數(shù):
var isString = isType("String"); //等價(jià)于 var isString = function (obj) { return Object.prototype.toString.call(obj) == "[object String]"; }
這種模式是不是有點(diǎn)似曾相識(shí)?是否有點(diǎn)像工廠模式?確實(shí)挺像的,只不過工廠模式是用來定制對(duì)象的,而這個(gè)是用來定制函數(shù)的。事實(shí)上這是一個(gè)閉包在js里的經(jīng)典技巧,它有一個(gè)很裝逼的名字函數(shù)柯里化。
為什么會(huì)這樣?之所以能實(shí)現(xiàn)這種效果,是因?yàn)殚]包的特性使得返回的匿名函數(shù)的作用域鏈一直保存著對(duì)type變量的引用。
什么意思呢,這里我想從另一個(gè)方面來解釋,假設(shè)js不存在閉包這個(gè)特性,那上面的代碼執(zhí)行效果又會(huì)變成什么樣?
按照一般的理解來說,在調(diào)用并執(zhí)行完isType("String")方法后,isType函數(shù)內(nèi)部變量都應(yīng)該被回收清除,變量type會(huì)被清空;也就是說當(dāng)我再調(diào)用isString(obj)時(shí),它得到的應(yīng)該是一個(gè)type變量為undefined的函數(shù):
var isString = function (obj) { return Object.prototype.toString.call(obj) == "[object undefined]"; }
undefined?什么鬼?為什么不是返回指定type="String"的函數(shù)?
事實(shí)上,return function(){} 形式返回的并不是一個(gè)函數(shù),而是一個(gè)函數(shù)的引用。什么是引用,簡單來說就是一個(gè)指向這個(gè)函數(shù)在內(nèi)存中的地址。也就是說這個(gè)返回來的匿名函數(shù)并沒有“定型”成真正的
var isString = function (obj) { return Object.prototype.toString.call(obj) == "[object String]"; }
它實(shí)際上還是這個(gè)函數(shù):
var isString = function (obj) { return Object.prototype.toString.call(obj) == "[object "+ type +"]"; }
既然如此,為什么我們可以成功的得到我們想要的函數(shù)?就是由于閉包特性導(dǎo)致isType()在執(zhí)行完后,垃圾回收器并沒有清空內(nèi)部變量type。沒有清空的原因是,內(nèi)部函數(shù)(返回的匿名函數(shù))的作用域鏈仍然保有對(duì) 外部函數(shù)(isType)的變量type的引用。JavaScript的垃圾回收器對(duì)于這種 保有引用的變量是不會(huì)清除的。
關(guān)于什么是作用域鏈以及作用域鏈和垃圾回收之間的具體關(guān)系,才是真正涉及閉包來龍去脈的真正原因,但是我要放到下一段講。這里我要再舉一個(gè)例子,以驗(yàn)證我前面所說的。
function f1(){ var n=999; nAdd=function(){n+=1} function f2(){ alert(n); } return f2; } var result=f1(); result(); // 999 nAdd(); result(); // 1000
這里盜用阮大俠的例子,相信很多朋友都會(huì)看過他這篇關(guān)于對(duì)閉包概念解釋的文章。這里算是做一個(gè)補(bǔ)充吧。
nAdd=function(){n+=1},js語法的書籍都講過,不以var 聲明的變量都會(huì)被默認(rèn)創(chuàng)建并提至全局變量中。雖然不推薦這種做法,容易造成全局變量污染和難以調(diào)試等問題,但是寫個(gè)小代碼測(cè)試就沒什么問題了。
f2被返回,并將f2的引用賦值給了result。由于f2函數(shù)的作用域鏈保有對(duì)n的引用,所以在執(zhí)行完f1()之后,n并沒有被回收清除。 這時(shí)再調(diào)用nAdd(),因?yàn)閚Add函數(shù)的作用域鏈也對(duì)n保有引用,所以在執(zhí)行n+1的操作后,所有引用這個(gè)n的地方都會(huì)+1。
對(duì)于非計(jì)算機(jī)科班出身的朋友看到這兩個(gè)名詞,心中會(huì)不會(huì)有一絲不安?其實(shí)他們并不難懂。
當(dāng)某個(gè)函數(shù)第一次被調(diào)用時(shí),會(huì)創(chuàng)建一個(gè)執(zhí)行環(huán)境及相應(yīng)的作用域鏈,并把作用域鏈賦值給一個(gè)特殊的內(nèi)部屬性,scope。然后使用this.arguments和其他命名參數(shù)的值來初始化函數(shù)的活動(dòng)對(duì)象。在作用域鏈中,外部函數(shù)的活動(dòng)對(duì)象始終處于第二位,外部函數(shù)的外部函數(shù)的活動(dòng)對(duì)象處于第三位,......直至作為作用域鏈終點(diǎn)的全局執(zhí)行環(huán)境。
function isType(type){ return function(obj){ return Object.prototype.toString.call(obj) == "[object "+ type + "]" } } var isString = isType("String"); var name = "Tom"; isString(name)//true
當(dāng)我第一次調(diào)用isString(name)時(shí),執(zhí)行環(huán)境會(huì)去創(chuàng)建一個(gè)包含this、arguments和obj的活動(dòng)對(duì)象。而外部函數(shù)的變量對(duì)象(this和type)在isString()執(zhí)行環(huán)境的作用域鏈中則處于第二位。全局的變量對(duì)象window則在isString()的作用域鏈中排第三位。作用域鏈上的變量對(duì)象的排列順序也就決定了執(zhí)行時(shí)變量查找的順序。這也解釋了,為什么當(dāng)外部有多個(gè)相同變量名的變量時(shí),解析器會(huì)取離它最近的那一個(gè)外部變量。
這里也說明了一個(gè)常用的開發(fā)技巧————緩存。
在函數(shù)內(nèi)部,緩存一個(gè)變量可以減少執(zhí)行器查找變量的次數(shù),提升執(zhí)行性能,因?yàn)樗偸俏挥谶@個(gè)執(zhí)行環(huán)境的作用域鏈上的第一位活動(dòng)對(duì)象中。
當(dāng)調(diào)用isType("String")之后,內(nèi)部函數(shù)執(zhí)行環(huán)境的作用域鏈就有了包含type變量的活動(dòng)對(duì)象,垃圾回收的機(jī)制之一就是 判斷一個(gè)對(duì)象是否存在被引用,如果是則不清除。而此時(shí)內(nèi)部函數(shù)被isString變量引用,所以在執(zhí)行完isString(name)后,內(nèi)部變量type依然存在。
總結(jié)閉包的濫用會(huì)導(dǎo)致一些副作用,比如內(nèi)存溢出、調(diào)試?yán)щy等。所以要慎用,清除閉包的方法就是消除引用。在該例中,令isString = null 即可清除引用。
閉包在js編程中有很多實(shí)用的技巧,這里由于本人精力不濟(jì),所以留著下次再說。88
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/86498.html
摘要:閉包的學(xué)術(shù)定義先來參考下各大權(quán)威對(duì)閉包的學(xué)術(shù)定義百科閉包,又稱詞法閉包或函數(shù)閉包,是引用了自由變量的函數(shù)。所以,有另一種說法認(rèn)為閉包是由函數(shù)和與其相關(guān)的引用環(huán)境組合而成的實(shí)體。 前言 上一章講解了閉包的底層實(shí)現(xiàn)細(xì)節(jié),我想大家對(duì)閉包的概念應(yīng)該也有了個(gè)大概印象,但是真要用簡短的幾句話來說清楚,這還真不是件容易的事。這里我們就來總結(jié)提煉下閉包的概念,以應(yīng)付那些非專人士的心血來潮。 閉包的學(xué)術(shù)...
摘要:有談?wù)劽嬖嚺c面試題對(duì)于前端面試的一些看法。動(dòng)態(tài)規(guī)劃算法的思想及實(shí)現(xiàn)方法幫大家理清動(dòng)態(tài)規(guī)劃的解決思路以及原理方法前端經(jīng)典面試題從輸入到頁面加載發(fā)生了什么這是一篇開發(fā)的科普類文章,涉及到優(yōu)化等多個(gè)方面。極客學(xué)院前端練習(xí)題道練習(xí)題,面試季練練手。 由數(shù)據(jù)綁定和排序引入的幾個(gè) JavaScript 知識(shí)點(diǎn) 在 JavaScript 的數(shù)據(jù)綁定和做簡單的表格排序中遇到的幾個(gè)知識(shí)點(diǎn) [[JS 基礎(chǔ)...
閱讀 3726·2023-04-25 22:43
閱讀 3727·2021-09-06 15:15
閱讀 1344·2019-08-30 15:54
閱讀 3584·2019-08-30 14:20
閱讀 2897·2019-08-29 17:16
閱讀 3125·2019-08-29 15:28
閱讀 3407·2019-08-29 11:08
閱讀 1079·2019-08-28 18:05