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