JavaScript閉包中難點深入分析
初識閉包
閉包可謂是JS的一大難點也是面試中常見的問題之一,今天開始梳理一下閉包的知識,請諸君品鑒。
什么是閉包
閉包是嵌套的內(nèi)部函數(shù);內(nèi)部函數(shù)包含被引用變量(函數(shù))的對象。閉包存在于嵌套的內(nèi)部函數(shù)中,例如在javascript中,只有函數(shù)內(nèi)部的子函數(shù)才能讀取局部變量,所以閉包可以理解成“定義在一個函數(shù)內(nèi)部的函數(shù)“。在本質(zhì)上,閉包是將函數(shù)內(nèi)部和函數(shù)外部連接起來。當(dāng)然如何直觀的查看閉包可以通過chrome來查看,這里有個坑需要反饋一下,新版的chrome需要先調(diào)用fun2()才允許debugger,這樣才能顯示閉包。
<script> function fn1(){ var a = 2; function fn2(){//執(zhí)行函數(shù)定義就會產(chǎn)生閉包(不用調(diào)用內(nèi)部函數(shù)) console.log(a); } //新版的chrome需要返回一下內(nèi)部函數(shù)才會顯示閉包 return fn2() } fn1() </script>
如何產(chǎn)生閉包
當(dāng)一個嵌套的內(nèi)部(子)函數(shù)引用了嵌套的外部(父)函數(shù)的變量(函數(shù))時,就產(chǎn)生了閉包
<script> // 將函數(shù)作為另一個函數(shù)的返回值 function fn1(){ var a = 2; function fn2(){// a++; console.log(a); } return fn2 //將一個內(nèi)部函數(shù)作為一個外部函數(shù)的返回值返回 } var f = fn1() //整個過程產(chǎn)生了一個閉包,主要看你產(chǎn)生了幾個內(nèi)部函數(shù)對象,調(diào)用了幾次外部函數(shù) //閉包的特點就是函數(shù)內(nèi)部的變量會一直存在于內(nèi)存中,不會立即釋放。 f()//3 這里的f()是調(diào)用了內(nèi)部函數(shù) f()//4 </script>
<script> // 將函數(shù)作為實參傳遞給另一個函數(shù)調(diào)用 function showDelay(msg,time){ //setTimeout 的第一個參數(shù)是函數(shù),符合閉包的規(guī)則 setTimeout(function(){ alert(msg) },time) } showDelay('張三',2000) </script>
產(chǎn)生閉包條件
函數(shù)嵌套;內(nèi)部函數(shù)引用了外部函數(shù)的數(shù)據(jù)(變量/函數(shù))。
閉包的作用
使用函數(shù)內(nèi)部的變量在函數(shù)執(zhí)行完畢后,仍然存活在內(nèi)存中(延長了局部變量的生命周期);讓函數(shù)外部可以操作(讀寫)到函數(shù)內(nèi)部的數(shù)據(jù)(變量/函數(shù))。
閉包的生命周期
產(chǎn)生:在嵌套的內(nèi)部函數(shù)定義執(zhí)行完時就產(chǎn)生了(不是在調(diào)用),死亡:在嵌套的內(nèi)部函數(shù)稱為垃圾對象時就死亡了。
<script> function fn1 () { //此時閉包就已經(jīng)產(chǎn)生了(函數(shù)提升,內(nèi)部函數(shù)對象已經(jīng)創(chuàng)建了) var a = 2; function fn2 () {// a++; console.log(a); } return fn2 //將一個內(nèi)部函數(shù)作為一個外部函數(shù)的返回值返回 } var f = fn1() f()//3 f()//4 f = null //閉包死亡(包含閉包的函數(shù)對象成為垃圾對象) </script>
閉包的應(yīng)用
定義JS模塊(具有特定功能的js文件),將所有的數(shù)據(jù)和功能都封裝在一個函數(shù)的內(nèi)部(私有的),只向外暴露一個包含n個方法的對象和函數(shù);模塊的使用者只需要通過模塊暴露的對象調(diào)用方法來實現(xiàn)對應(yīng)的功能。
//myModule.js 文件 function myModule(){ // 私有數(shù)據(jù) var msg = 'My Module' function showUpper(){ console.log('showUpper' +msg.toUpperCase()); } function showLow(){ console.log('showLow' +msg.toLowerCase()); } //向外暴露對象(給外部使用的方法) return { showUpper:showUpper, showLow:showLow } } //index.html文件 <script src="./myModule.js"></script> <script> var module = myModule() module.showUpper() module.showLow() </script>
我們也可以通過匿名函數(shù)來實現(xiàn)閉包,這樣能很便捷的調(diào)用閉包里面的屬性,雖然會達(dá)到我們想要的效果,但是可能會造成全局的變量名污染,建議使用第一種。
//myModule2.js文件 (function(){ // 私有數(shù)據(jù) var msg = 'My Module' // 操作數(shù)據(jù)的函數(shù) function showUpper(){ console.log('showUpper' +msg.toUpperCase()); } function showLow(){ console.log('showLow' +msg.toLowerCase()); } //向外暴露對象(給外部使用的方法) window.myModule2 = { showUpper:showUpper, showLow:showLow } })() //index.js文件 <script src="./myModule2.js"></script> <script> myModule2.showUpper() myModule2.showLow() </script
閉包的缺點及解決方法
在我們使用閉包過程中,函數(shù)執(zhí)行完后,函數(shù)內(nèi)部的局部變量沒有釋放,占用內(nèi)存時間會變長,容易造成內(nèi)存泄漏,所以在日常開發(fā)中,盡量避免閉包的出現(xiàn),或者要對局部變量及時釋放。
<script> function fn1(){ var arr = new Array[100000] function fn2(){ console.log(arr.length); } return fn2 } var f = fn1() f() //不用閉包或者回收閉包 f = null//讓內(nèi)部函數(shù)成為垃圾對象 --> 回收閉包 </script>
內(nèi)存溢出:一種程序運行出現(xiàn)的錯誤,當(dāng)程序運行需要的內(nèi)存超過了剩余的內(nèi)存時,就會拋出內(nèi)存溢出的錯誤。
<script> var obj = {} for(var i=0;i<10000;i++){ obj[i]=new Array(1000000) console.log('------'); } </script>
內(nèi)存泄漏:占用的內(nèi)存沒有及時釋放,內(nèi)存泄漏積累多了就容易導(dǎo)致內(nèi)存溢出。常見的內(nèi)存泄漏:意外的全局變量、沒有及時清理的計時器或回調(diào)函數(shù)、閉包。
<script> //意外的全局變量 function fn(){ a = 10; console.log(a); } // 調(diào)用函數(shù)雖然能打印a,但是a并沒有被釋放掉。一不注意就設(shè)置了一個全局變量 fn() //沒有及時清理計時器或回調(diào)函數(shù) var intervalId = setInterval(function(){ //啟動循環(huán)定時器后不清理 console.log('--------'); },2000) // clearInterval(intervalId) //閉包 function fn1(){ var a = 2 //閉包 a 并沒有被釋放掉 function fn2(){ console.log(++a) } return fn2 } var f = fn1() f() // f = null 不執(zhí)行這條語句,a的值一直在 </script>
閉包案例
<script> // 案例一: var name = "this is Window" var object = { name:"this is Object", getName:function(){ return function(){ return this.name } } } //閉包的this只能是全局,若在當(dāng)前作用域中定義了this,就直接使用定義的this,若沒定義,則需要一層層向外找,直到全局為止 //本題是沒有閉包的 alert(object.getName()())//this is Window // 案例二: var name1 = "this is Window" var object1 = { name1:"this is Object", getName:function(){ //定義的that形成了閉包,內(nèi)部函數(shù)引用了外部函數(shù)的變量,而this指向的是object,所以返回的是object中的name1 var that = this; return function(){ return that.name1 } } } alert(object1.getName()())// this is Object </script>
<script> //沒有使用閉包的話,數(shù)據(jù)是沒有保留的,所以n傳遞給o之后,下次運算o值還是上次的值不會發(fā)生改變 function fun(n,o){ console.log(o); return{ fun:function(m){ return fun(m,n) } } } //在執(zhí)行fun(0)之后,n被之前的n=0,一直被調(diào)用 var a = fun(0); //閉包里面的n傳入了0 a.fun(1); a.fun(2); a.fun(3)//undefined,0,0,0 //鏈?zhǔn)綀?zhí)行會導(dǎo)致n的改變,n是前面函數(shù)執(zhí)行的形參 var b = fun(0).fun(1).fun(2).fun(3)//undefined,0,1,2 //c.fun(2)、c.fun(3)都調(diào)用了fun(1)留下的閉包n var c = fun(0).fun(1); c.fun(2); c.fun(3)//undefined,0,1,1 </script>
到此這篇關(guān)于JavaScript閉包中難點深入分析的文章就介紹到這了,更多相關(guān)JS閉包內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
正則表達(dá)式判斷是否存在中文和全角字符和判斷包含中文字符串長度
對于一些更安全的容錯嚴(yán)重,需要用到2008-09-09一文熟練掌握J(rèn)avaScript的switch用法
在JavaScript中switch語句是一種用于多條件分支的控制語句,下面這篇文章主要給大家介紹了關(guān)于如果通過一文熟練掌握J(rèn)avaScript的switch用法的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-01-01