摘要:通過例子再來回顧一下閉包的理解所謂的閉包就是可以創建一個獨立的環境,每個閉包里面的環境都是獨立的,互不干擾。此時就是一個閉包,這樣寫有些麻煩,我們對此改進一下。參考文章初識中的閉包從閉
走在前端的大道上
本篇將自己讀過的相關 javascript閉包 文章中,對自己有啟發的章節片段總結在這(會對原文進行刪改),會不斷豐富提煉總結更新。
首先著重回顧一下 全局變量 和 局部變量 全局變量:可以在任意位置訪問的量就叫 全局變量
var age = 20; function a(){ console.log(age); //20 } a();局部變量:函數中用var定義的變量,只能在函數中訪問這個變量,函數外部訪問不了。
function a(){ var age = 20; } a(); console.log(age); // Uncaught ReferenceError: age is not defined
重點:
1.在函數中如果不使用var定義變量那么js引擎會自動添加成全局變量。函數的相關知識點:
2.全局變量從創建的那一刻起就會一直保存在內存中,除非你關閉這個頁面,局部變量當函數運行完以后就會銷毀這個變量,假如有多次調用這個函數它下一次調用的時候又會重新創建那個變量,既運行完就銷毀,回到最初的狀態,簡單來說局部變量是一次性的,用完就扔,下次要我再重新創建。
1.一個函數內可以嵌套多個函數
2.函數里面的子函數可以訪問它上級定義的變量,注意不只是一級,如果上級沒有會繼續往上級找,直到找到為止,如果找到全局變量到找不到就會報錯。
function a(){ var name = "追夢子"; function b(){ console.log(name); // "追夢子" } b(); } a();
3.函數的另外一種調用形式,你可以把它叫做自調用,自己調用自己,達到自執行的效果。
var a = 0; (function(){ console.log(++a); // 1 })()
這種方式用()把內容包裹起來,后面的()表示執行這個函數,可能你會問為什么要把函數包起來,如果不包裹起來,js會把它當作函數聲明來處理,如果包裹起來就是表達式。
正題閉包說得通熟易懂一點,就是指 有權訪問另一個函數作用域變量的函數。閉包可以解決函數外部無法訪問函數內部變量的問題。創建閉包的常見方式,就是在一個函數內部創建另外一個函數,并返回。
下面是一段沒有使用閉包的代碼:
function fn(){ var a = 10; //報錯 } alert(a);
因為a沒有定義,雖然函數fn里面定義了a但是,但是它只能在函數fn中使用。也就是作用域的問題。
function fn(){ //定義了一個變量name var name = "追夢子"; //外部想訪問這個變量name怎么辦?return!把它返回出去,再用個變量接收一下不就可以了 return name; } var name = fn();//接收fn返回的name值。 alert(name);//追夢子;
這里的閉包就是利用函數的return。除了通過return還可以通過其他的幾種方法如下:
方法1:
function fn(){ var a = 0; b = a; } alert(b)
這里利用了js的一個特性,如果在函數中沒有用var定義變量,那么這個變量屬于全局的,但這種方法多少有些不好。
方法2:
var f = null; function fn(){ var a = 0; f = function(){ a++; f.a = a; }; } fn(); f(); alert(f.a);//1 f(); alert(f.a);//2
其實閉包還有一個很重要的特性,來看一個例子。
var lis= document.getElementsByTagName["li"]; //假如這段代碼中的lis.length = 5; for(var i=0;i最終結果是不管單擊哪個li元素都是彈5。不信你試試。為什么呢??聪旅娣治?。
for(var i=0;i為什么會這樣呢,因為你for循環只是給li綁定事件,但是里面的函數代碼并不會執行啊,這個執行是在你點擊的時候才執行的好吧?但是此時的i已經是5了,所以所有的都打印出5來了。
for(var i=0;i閉包的特點不只是讓函數外部訪問函數內部變量這么簡單,還有一個大的特點就是 通過閉包可以讓函數中的變量持久保持。
function fn(){ var num = 5; num+=1; alert(num); } fn(); //6 fn(); //6為什么都是 6 呢?因為 函數一旦調用里面的內容就會被銷毀,下一次調用又是一個新的函數,和上一個調用的不相關了。
劃重點:JavaScript中有回收機制,函數沒有被引用 且執行完以后這個函數的作用域就會被銷毀,如果一個函數被其他變量引用,這個函數的作用域將不會被銷毀,(簡單來說就是函數里面的變量會被保存下來,你可以理解成全局變量。)
再來
function fn(){ var num = 0; return function(){ num+=1; alert(num); }; } var f = fn(); f(); //1 f(); //2定義了一個fn函數,里面有個num默認為0,接著返回了一個匿名函數(也就是沒有名字的函數)。我們在外部用 f 接收這個返回的函數。這個匿名函數干的事情就是把 num 加 1,還有我們用來調試的 alert 。
這里之所以執行完這個函數 num 沒有被銷毀,是因為那個匿名函數的問題,因為這個匿名函數用到了這個 num,所以沒有被銷毀,一直保持在內存中,因此我們 f() 時 num 可以一直加。
function a(){ var aa = 0; function b(){ aa ++; console.log(aa); } return b; } var ab = a(); ab(); //1 ab(); //2里面的變量的值沒有被銷毀,因為函數a被外部的變量ab引用,所以變量aa沒有被回收。
如果某個函數被它的父函數之外的一個變量引用,就形成了一個閉包
還有一種更為常用的閉包寫法
var bi = (function(){ var a = 0; function b(){ a ++; console.log(a); } return b; })(); bi(); //1 bi(); //2 bi(); //3執行過程分析:
首先把一個自執行函數賦值給了bi,這個自執行函數運行完成以后就bi的值就變成了
function b(){ a ++; console.log(a); }因為我們在上面的代碼 return 回去了 b,然后因為這個自執行函數被 bi 引用所以里面的變量 a 并沒有因為這個自執行函數執完而銷毀,而是保存到了內存中,所以我們多次打印 bi()
就成了1、2、3閉包是使用可以帶來以下好處:
希望一個變量長期駐扎在內存中
避免全局變量的污染
私有成員的存在
閉包可以讀取到函數內部的變量,這是由于閉包后函數的堆棧不會釋放,也就是說這些值始終保持在內存中。這是一個優點,也是一個缺點。
通過例子再來回顧一下
閉包的理解:
所謂的閉包就是可以創建一個獨立的環境,每個閉包里面的環境都是獨立的,互不干擾。
閉包的創建:
一個函數中嵌套另外一個函數,并且將這個函數return出去,然后將這個return出來的函數保存到了一個變量中,那么就創建了一個閉包。var arr = []; for(var i=0;i<2;i++){ arr[i] = function(){ console.log(i); } } arr[0](); //2 arr[1](); //2實際情況我們是要打印0,1,2,3這樣的數,但是每次都是打印2,什么情況呢?雖然我們在for中給arr的每個值添加了一個匿名函數,但是 在for循環中我們并沒有執行這個函數,而是在for循環以后執行的這個函數,那么自然打印出來的就是for循環完以后i的值。
var arr = []; // for(var i=0;i<2;i++){ var i = 2; arr[0] = function(){ console.log(i); } arr[1] = function(){ console.log(i); } // } arr[0](); //2 arr[1](); //2相當于這樣,雖然這個函數沒有執行,但是arr的i已經給執行了,因為arr不是函數啊,肯定是執行的,而你函數沒有調用自然就不會執行,當函數調用的時候i已經是2了。既然如此只要我們在for循環的時候直接執行這個函數就ok。
var arr = []; for(var i=0;i<2;i++){ arr[i] = function(){ console.log(i); } arr[i](); } //0 //1這樣,在每一次for循環的時候就直接執行了這個函數,打印正常,但是我們這樣同樣有一個問題,那就是在每一次for循環的時候這個函數就已經被執行了,我們要的是我們想什么時候調用就時候調用,而不是直接在for執行中直接執行,那么顯然這樣做是達不到我們的目的的。
現在我們在想想閉包的概念,閉包可以創建獨立的環境,并且每一個閉包的環境是獨立的,也就是說,我們可以通過閉包來保存這些不同的變量。
我們回顧一下閉包的創建方法:一個函數中嵌套另外一個函數,并且將這個函數return出去,然后將這個return出來的函數保存到了一個變量中,那么就創建了一個閉包。
var arr = []; for(var i=0;i<2;i++){ arr[i] = a(i); } function a(i){ return function(){ console.log(i); } } arr[0](); //0 arr[1](); //1此時就是一個閉包,這樣寫有些麻煩,我們對此改進一下。
var arr = []; for(var i=0;i<3;i++){ arr[i] = (function(i){ return function(){ console.log(i); } })(i) } arr[0](); //0 arr[1](); //1 arr[2](); //2還可以這樣
var arr = []; for(var i=0;i<3;i++){ (function(i){ arr[i] = function(){ console.log(i); } })(i) } arr[0](); //0 arr[1](); //1 arr[2](); //2此時 arr 里面的 i 用的是閉包里面的 i,而不是 for 中的 i,因為我們說過每個閉包的環境都是獨立的。
js中的回收機制function a(){ var num = 10; return function(){ num ++; console.log(num); } } a()(); //11 a()(); //11按理說第二次執行函數a的時候應該打印出12才對,但是打印的卻是11。
首先來看看我們的理解
1.我們在函數a中返回了一個匿名函數,在這個匿名函數中我們num++了一下,然后我們在函數外面執行了這個匿名函數函數,(第一括號執行函數a第二個括號執行這個rutrun回去的函數)
2.現在num是11,然后我們又執行了一次這個函數,你們應該是12吧,為什么不是呢?實際js的執行
但是js的設計者為了讓沒有必要的變量保存在內存中,(我們寫的任何變量都是需要內存空間的),什么叫沒有必要的變量?也就是說你不在需要這個變量的時候它就會被銷毀?那么你肯定會問js怎么知道那些變量是我們不需要的哪些是我們需要的。所以js為了知道哪些變量需要保存下來,哪些不需要保存下來,會進行一些判斷。接下來我們就一起看看js是怎么判斷的。
1.在js中定義的全局變量是不會被銷毀的,因為我們隨時都可能會用到這個變量,所以不能被銷毀。
2.但是在函數中定義的變量就不一定了,而且由于在函數的定義的變量的生命周期在執行完這個函數就銷毀的原因自然就保存不了上一次的值。
3.但是并不是說函數就真的保存不了上一次的值,因為有的時候我們確實需要上一次的值,所以js判斷是否需要保存上一次變量的值的時候就會遵守這樣一個規則:
如果這個函數有被外部的變量引用就不會銷毀(這句話說的不夠準確,下面代碼會一步一步解釋),否則銷毀。怎么理解這句話呢?
function a(){ var b = 0; return function(){ b ++; console.log(b); } } var d = a(); d();//1 d();//2函數a被變量d引用,更準確的說是函數a里面的那個匿名函數被變量d所引用,因為變量d等于的是函數a執行完成后的值,而函數a執行完以后又因為函數a返回了那個匿名函數,所以準確的說是變量d等于匿名函數。而這個匿名函數因為使用了函數a中的變量b并且還被變量d所引用,所以就形成了一個閉包,只要這個變量d不等于null的話,那么那個變量b會一直保存到變量d中不會被銷毀。
總結:
1、如果一個對象不被引用,那么這個對象就會被GC回收;
2、如果兩個對象互相引用,但是沒有被第3個對象所引用,那么這兩個互相引用的對象也會被回收。參考文章:
1.初識js中的閉包
2.從閉包案例中學習閉包的作用,會不會由你
3.那些年我們一起過的JS閉包,作用域,this,讓我們一起劃上完美的句號
4.再次講解js中的回收機制是怎么一回事。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/92071.html
摘要:引擎對堆內存中的對象進行分代管理新生代存活周期較短的對象,如臨時變量字符串等。內存泄漏對于持續運行的服務進程,必須及時釋放不再用到的內存。 (關注福利,關注本公眾號回復[資料]領取優質前端視頻,包括Vue、React、Node源碼和實戰、面試指導) 本周正式開始前端進階的第一期,本周的主題是調用堆棧,今天是第4天。 本計劃一共28期,每期重點攻克一個面試重難點,如果你還不了解本進階計劃...
摘要:由于函數被調用了,線程會從剛剛保存的變量中取出內容,去解析執行它。最后,當線程遇到離開上下文的標識,便離開上下文,并把的結果一并返回出去。 原文鏈接,歡迎關注我的博客 我相信很多前端初學者一開始都會被執行上下文這個概念弄暈,或者說似懂非懂。對于工作兩年的我來說,說來實在慚愧,雖然知道它大概是什么,但總覺得沒有一個更為清晰的認識(無法把它的工作過程描述清楚),因此最近特意溫習了一遍,寫下...
摘要:使用上一篇文章的例子來說明下自由變量進階期深入淺出圖解作用域鏈和閉包訪問外部的今天是今天是其中既不是參數,也不是局部變量,所以是自由變量。 (關注福利,關注本公眾號回復[資料]領取優質前端視頻,包括Vue、React、Node源碼和實戰、面試指導) 本周正式開始前端進階的第二期,本周的主題是作用域閉包,今天是第7天。 本計劃一共28期,每期重點攻克一個面試重難點,如果你還不了解本進階計...
摘要:前言函數式編程在前端已經成為了一個非常熱門的話題。整個過程就是體現了函數式編程的核心思想通過函數對數據進行轉換。高階函數函數式編程傾向于復用一組通用的函數功能來處理數據,它通過使用高階函數來實現。 前言 函數式編程在前端已經成為了一個非常熱門的話題。在最近幾年里,我們看到非常多的應用程序代碼庫里大量使用著函數式編程思想。 本文將略去那些晦澀難懂的概念介紹,重點展示在 JavaScrip...
摘要:閉包面試題解由于作用域鏈機制的影響,閉包只能取得內部函數的最后一個值,這引起的一個副作用就是如果內部函數在一個循環中,那么變量的值始終為最后一個值。 (關注福利,關注本公眾號回復[資料]領取優質前端視頻,包括Vue、React、Node源碼和實戰、面試指導) 本周正式開始前端進階的第二期,本周的主題是作用域閉包,今天是第8天。 本計劃一共28期,每期重點攻克一個面試重難點,如果你還不了...
閱讀 4133·2021-11-22 13:52
閱讀 2500·2021-11-22 13:52
閱讀 3672·2021-11-19 09:59
閱讀 1173·2021-11-17 09:33
閱讀 2435·2019-08-30 10:53
閱讀 1191·2019-08-29 17:28
閱讀 1297·2019-08-29 17:03
閱讀 3087·2019-08-26 11:31