您好,登錄后才能下訂單哦!
這篇文章主要介紹javascript如何定義閉包,文中介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們一定要看完!
閉包 永遠(yuǎn)都是前端開發(fā)者繞不過去的一個(gè)坎,不管你喜歡與否,在工作和面試中,都會(huì)遇到。每個(gè)人對(duì)閉包的理解都不盡相同,這里筆者談?wù)勛陨韺?duì)閉包的理解。(如果與您的理解有出入,請(qǐng)以您自己為準(zhǔn) )
在給出定義之前,不妨看看別人是如何定義閉包的:
函數(shù)對(duì)象可以通過作用域鏈相互關(guān)聯(lián)起來,函數(shù)體內(nèi)部的變量都可以保存在函數(shù)作用域內(nèi),這種特性在計(jì)算機(jī)科學(xué)文獻(xiàn)中稱為“閉包” -- JavaScript權(quán)威指南(第六版)
閉包是指有權(quán)訪問另一個(gè)函數(shù)作用域中的變量的函數(shù)。創(chuàng)建閉包的常見方式,就是在一個(gè)函數(shù)內(nèi)部創(chuàng)建另一個(gè)函數(shù)。 -- JavaScript高級(jí)程序設(shè)計(jì)(第三版)
當(dāng)函數(shù)可以記住并訪問所在的詞法作用域時(shí),就產(chǎn)生了閉包,即使函數(shù)是在當(dāng)前詞法作用域之外執(zhí)行。 -- 你不知道的JavaScript(上卷)
雖然上面的幾段話描述起來并不一樣,但是您細(xì)細(xì)品味后還是能找出一些共同點(diǎn)。其中最重要的是不同作用域之間的聯(lián)系。當(dāng)然了,您可以直接引用上面的定義(畢竟上面幾個(gè)定義還是比較權(quán)威的),這里筆者比較喜歡最后一段的定義,同時(shí)力推《你不知道的JavaScript(上卷)》這本書,值得反復(fù)細(xì)讀。
光給出定義是遠(yuǎn)遠(yuǎn)不夠的,還必須探討內(nèi)部涉及了哪些知識(shí)點(diǎn)。下面是筆者認(rèn)為有用到的知識(shí)點(diǎn)。
嗯,其實(shí)筆者知道你們都想到了這點(diǎn)(不會(huì)吧,不會(huì)有人沒想到這點(diǎn)吧)。既然大家都了解作用域。這里就簡單描述一下,過一下場即可。
作用域:根據(jù)名稱查找變量的一套規(guī)則。分為三種類型:全局作用域;函數(shù)作用域;塊作用域。
需要注意的是塊作用域,ES6新增的規(guī)范。 在花括號(hào){}
里面使用let,const
定義的變量,都會(huì)綁定到該作用范圍內(nèi),花括號(hào)以外的地方無法訪問。注意:在花括號(hào)開始 到 let變量聲明之前,存在暫時(shí)性死區(qū)
(該點(diǎn)不在本文討論范圍)。
作用域鏈:當(dāng)不同的作用域 (混~淆~在~一~起~ 呸,不小心出戲了) 圈套在一起時(shí),就形成了作用域鏈。注意的是,查找方向是從內(nèi)到外的。
為什么作用域的查找方向是從內(nèi)到外的呢?這是個(gè)很有趣的問題。個(gè)人覺得是跟js執(zhí)行函數(shù)的入棧方式?jīng)Q定的(感覺有點(diǎn)偏題了,有興趣的小伙伴可以去查一下資料)。
函數(shù)之所以 可以訪問另一個(gè)函數(shù)作用域的變量(或者說記住當(dāng)前的作用域并在當(dāng)前以外的地方訪問)的關(guān)鍵點(diǎn)
就是詞法作用域
在起作用。這一點(diǎn)很重要,但不是所有人都知道這個(gè)知識(shí)點(diǎn),這里簡單探討一下。
在編程界中,存在兩種作用域工作模式,一種是被大多數(shù)編程語言所采用的
詞法作用域
;另一種就是與其相反的動(dòng)態(tài)作用域
(這個(gè)不在本文的討論范圍)。
詞法作用域: 變量和塊的作用域 在 您編寫代碼的階段 就已經(jīng)確定好了,不會(huì)隨著調(diào)用的對(duì)象或者地方的不同而改變(感覺跟this相反)。
要不,舉個(gè)栗子看看吧:
let a = 1;function fn(){ let a = 2; function fn2(){ console.log(a); } return fn2; }let fn3 = fn(); fn3();復(fù)制代碼
從上面的定義可以知道,fn
是一個(gè)閉包函數(shù),fn3
拿到了fn2
的指針地址,當(dāng)fn3
執(zhí)行的時(shí)候,其實(shí)是執(zhí)行fn2
,而里面的a
變量,根據(jù)作用域鏈的查找規(guī)則,找到的是fn
作用域內(nèi)的變量a
,所以最終的輸出是2,不是1。(可以看下圖)
雖然詞法作用域是靜態(tài)的,但依然有辦法可以欺騙它,達(dá)到動(dòng)態(tài)的效果。
第一種方法是使用eval. eval可以把字符串解析成一個(gè)腳本來運(yùn)行,由于在詞法分析階段,無法預(yù)測eval運(yùn)行的腳本,所以不會(huì)對(duì)其進(jìn)行優(yōu)化分析。
第二種方法是with. with通常被當(dāng)作重復(fù)引用同一個(gè)對(duì)象中的多個(gè)屬性的快捷方式,可以不需要重復(fù)引用對(duì)象本身。with本身比較難掌握,使用不當(dāng)容易出現(xiàn)意外情況(如下例子),不推薦使用 -.-
function Fn(obj){ with(obj){ a = 2; } }var o1 = { a:1}var o2 = { b:1} Fn(o1);console.log(o1.a); //2Fn(o2);console.log(o2.a); //undefined;console.log(a); //2 a被泄漏到全局里面去了// 這是with的一個(gè)副作用, 如果當(dāng)前詞法作用域沒有該屬性,會(huì)在全局創(chuàng)建一個(gè)復(fù)制代碼
閉包的使用場景可多了,平時(shí)使用的插件或者框架,基本上都有閉包的身影,可能您沒留意過罷了。下面筆者列舉一些比較常見的場景。
模擬私有變量和方法,進(jìn)一步來說可以是模擬模塊化
;目前常用的AMD,CommonJS等模塊規(guī)范,都是利用閉包的思想;
柯里化函數(shù)或者偏函數(shù);利用閉包可以把參數(shù)分成多次傳參。如下面代碼:
// 柯里化函數(shù)function currying(fn){ var allArgs = []; function bindCurry(){ var args = [].slice.call(arguments); allArgs = allArgs.concat(args); return bindCurry; } bindCurry.toString = function(){ return fn.apply(null, allArgs); }; return bindCurry; }復(fù)制代碼
實(shí)現(xiàn)防抖或者節(jié)流函數(shù);
實(shí)現(xiàn)緩存結(jié)果(記憶化)的輔助函數(shù):
// 該方法適合緩存結(jié)果不易改變的函數(shù)const memorize = fn => { let memorized = false; let result = undefined; return (...args) => { if (memorized) { return result; } else { result = fn.apply(null,args); memorized = true; fn = undefined; return result; } }; };復(fù)制代碼
說了那么多,我怎么知道自己寫的代碼是不是閉包呢?先不說新手,有些代碼的確隱藏的深,老鳥不仔細(xì)看也可能發(fā)現(xiàn)不了。 那有沒有方法可以幫助我們區(qū)分一個(gè)函數(shù)是不是閉包呢?答案是肯定的,要學(xué)會(huì)善于利用周邊的工具資源,比如瀏覽器。
打開常用的瀏覽器(chrome或者其他),在要驗(yàn)證的代碼中打上debugger斷點(diǎn),然后看控制臺(tái),在scope里面的Closure(閉包)里面是否有該函數(shù)(如下圖)。
答案是有可能。內(nèi)存泄漏的原因在于垃圾回收(GC)無法釋放變量的內(nèi)存,導(dǎo)致運(yùn)行一段時(shí)候后,可用內(nèi)存越來越少,最終出現(xiàn)內(nèi)存泄漏的情況。常見的內(nèi)存泄漏場景有4種:全局變量;閉包引用;DOM事件綁定;不合理使用緩存。其中,閉包導(dǎo)致內(nèi)存泄漏都是比較隱蔽的,用肉眼查看代碼判斷是比較難,我們可用借助chrome瀏覽器的Memory標(biāo)簽欄工具來調(diào)試。由于篇幅問題,不展開說明了,有興趣自己去了解一下如何使用。
以上是javascript如何定義閉包的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對(duì)大家有幫助,更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。