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