摘要:在前端開發中閉包是一個很重要的知識點,是面試中一定會被問到的內容。閉包的用途閉包可以用在許多地方。這里僅僅是我對閉包的一些見解,若有錯誤的地方,還望大家提出,一起交流共同進步參考文獻你不知道的上卷深入理解系列
在前端開發中閉包是一個很重要的知識點,是面試中一定會被問到的內容。之前我對閉包的理解主要是"通過閉包可以在函數外部能訪問到函數內部的變量",對閉包運用的也很少,甚至自己寫過閉包自己都不太清楚,只知道這樣寫可以解決問題。最近在梳理自己的js知識點,發現自己對js閉包理解的很不透徹,于是想全面的分析一下閉包,特別是閉包的形成原因和閉包的使用場景。
閉包的定義閉包是指有權訪問另一個函數作用域中的變量的函數 --《JavaScript高級程序設計》
函數對象可以通過作用域關聯起來,函數體內的變量都可以保存在函數作用域內,這在計算機科學文獻中稱為“閉包”,所有的javascirpt函數都是閉包 --《Javascript權威指南》
看完這些專業的定義是不是感覺一頭霧水,沒關系,我和一樣也沒明白這些定義說的是啥,咱接著往下分析。
在認識閉包原理之前我們先必須對作用域、執行上下文、執行上下文堆棧、變量對象、活動對象、作用域鏈有著全面的認識
作用域 Scope作用域是一套規則,用于確定在何處以及如何查找變量(標識符)
作用域共有兩種主要的工作模型:
詞法作用域:作用域是在編寫代碼的時候確定的
動態作用域:作用域是在代碼運行的時候確定的
我們知道javascript使用的是詞法作用域
執行上下文 Execution ContextsJavascript中代碼的執行上下文分為以下三種:
全局級別的代碼 – 這個是默認的代碼運行環境,一旦代碼被載入,引擎最先進入的就是這個環境。
函數級別的代碼 – 當執行一個函數時,運行函數體中的代碼。
Eval的代碼 – 在Eval函數內運行的代碼。
一個執行的上下文可以抽象的理解為一個對象。每一個執行的上下文都有一系列的屬性(變量對象(variable object),this指針(this value),作用域鏈(scope chain) )
Execution Contexts = { variable object:變量對象; this value: this指針; scope chain:作用域鏈; }執行上下文堆棧 Execution Contexts Stack
活動的執行上下文組在邏輯上組成一個堆棧。堆棧底部永遠都是全局上下文(globalContext),而頂部就是當前(活動的)執行上下文。
當add函數被調用時,add函數執行上下文被壓入執行上下文堆棧的頂端,此時執行上下文堆??杀硎緸椋?/p>
EC Stack = [functionContext globalContext ];
add函數執行完畢后,其執行上下文將會從執行上下文堆棧頂端彈出并被銷毀。全局執行上下文只有在瀏覽器關閉時才會從執行上下文堆棧中銷毀
變量對象 Variable Object如果變量與執行上下文相關,那變量自己應該知道它的數據存儲在哪里,并且知道如何訪問。這種機制稱為變量對象(variable object)。
可以說變量對象是與執行上下文相關的數據作用域(scope of data) 。它是與執行上下文關聯的特殊對象,用于存儲被定義在執行上下文中的變量(variables)、函數聲明(function declarations) 。
當進入全局上下文時,全局上下文的變量對象可表示為:
VO = { add:活動對象 Activation Object, sum: undefined, Math: <...>, String: <...> ... window: global //引用自身 }
當函數被調用者激活時,這個特殊的活動對象(activation object) 就被創建了。它包含普通參數(formal parameters) 與特殊參數(arguments)對象(具有索引屬性的參數映射表)?;顒訉ο笤诤瘮瞪舷挛闹凶鳛樽兞繉ο笫褂谩?
當add函數被調用時,add函數執行上下文被壓入執行上下文堆棧的頂端,add函數執行上下文中活動對象可表示為
AO = { num: 4, sum :5, arguments:{0:4} }作用域鏈 Scope Chain
函數上下文的作用域鏈在函數調用時創建的,包含活動對象AO和這個函數內部的[[scope]]屬性。
var x = 10; function foo() { var y = 20; function bar() { var z = 30; alert(x + y + z); } bar(); } foo();
在這段代碼中我們看到變量"y"在函數"foo"中定義(意味著它在foo上下文的AO中)"z"在函數"bar"中定義,但是變量"x"并未在"bar"上下文中定義,相應地,它也不會添加到"bar"的AO中。乍一看,變量"x"相對于函數"bar"根本就不存在;
函數"bar"如何訪問到變量"x"?理論上函數應該能訪問一個更高一層上下文的變量對象。實際上它正是這樣,這種機制是通過函數內部的[[scope]]屬性來實現的。
[[scope]]是所有父級變量對象的層級鏈,處于當前函數上下文之上,在函數創建時存于其中。
注意: [[scope]]在函數創建時被存儲是靜態的(不變的),直至函數銷毀。即:函數可以永不調用,但[[scope]]屬性已經寫入,并存儲在函數對象中。
在這里我們逐步分析下
全局上下文的變量對象是:
globalContext.VO === Global = { x: 10 foo:};
在"foo"創建時,"foo"的[[scope]]屬性是:
foo.[[Scope]] = [ globalContext.VO ];
在"foo"激活時(進入上下文),"foo"上下文的活動對象是:
fooContext.AO = { y: 20, bar:};
"foo"上下文的作用域鏈為:
fooContext.Scope = [ fooContext.AO, globalContext.VO ];
內部函數"bar"創建時,其[[scope]]為:
bar.[[Scope]] = [ fooContext.AO, globalContext.VO ];
在"bar"激活時,"bar"上下文的活動對象為:
barContext.AO = { z: 30 };
"bar"上下文的作用域鏈為:
bar.Scope= [ barContext.AO, fooContext.AO, globalContext.VO ];
介紹了一大堆內容,是不是有點暈忽忽的?堅持一下,下面是重點
閉包的原理我們通過一個閉包的例子來分析一下閉包的形成原理
function add(){ var sum =5; var func = function () { console.log(sum); } return func; } var addFunc = add(); addFunc(); //5
js執行流進入全局執行上下文環境時,全局執行上下文可表示為:
globalContext = { VO: { add:, addFunc: undefined }, this: window, scope chain: window }
當add函數被調用時,add函數執行上下文可表示為:
addContext = { AO: { sum: undefined //代碼進入執行階段時此處被賦值為5 func: undefined //代碼進入執行階段時此處被賦值為function (){console.log(sum);} }, this: window, scope chain: addContext.AO + globalContext.VO }
add函數執行完畢后,js執行流回到全局上下文環境中,將add函數的返回值賦值給addFunc。
由于addFunc仍保存著func函數的引用,所以add函數執行上下文從執行上下文堆棧頂端彈出后并未被銷毀而是保存在內存中。
當addFunc()執行時,func函數被調用,此時func函數執行上下文可表示為:
funcContext = { this: window, scope chain: addContext.AO + globalContext.VO }
當要訪問變量sum時,func的活動對象中未能找到,則會沿著作用域鏈查找,由于js遵循詞法作用域,作用域在函數創建階段就被確定,在add函數的活動對象中找到sum = 5;
介紹到這里你明白形成閉包的原因了嗎?
Javascript允許使用內部函數---即函數定義和函數表達式位于另一個函數的函數體內。而且,這些內部函數可以訪問它們所在的外部函數中聲明的所有局部變量、參數和聲明的其他內部函數。當其中一個這樣的內部函數在包含它們的外部函數之外被調用時,就會形成閉包。
閉包可以用在許多地方。它的最大用處有兩個,一個是前面提到的可以讀取函數內部的變量,另一個就是讓這些變量的值始終保持在內存中。
1. 保護變量的安全實現JS私有屬性和私有方法利用閉包可以讀取函數內部的變量,變量在函數外部不能直接讀取到,從而達到保護變量安全的作用。因為私有方法在函數內部都能被訪問到,從而實現了私有屬性和方法的共享。
常見的模塊模式就是利用閉包的這種特性建立的
var Counter = (function() { //私有屬性 var privateCounter = 0; //私有方法 function changeBy(val) { privateCounter += val; } return { increment: function() { changeBy(1); }, decrement: function() { changeBy(-1); }, value: function() { return privateCounter; } } })(); console.log(privateCounter); //privateCounter is not defined console.log(Counter.value()); // 0 Counter.increment(); Counter.increment(); console.log(Counter.value()); // 2 Counter.decrement(); console.log(Counter.value()); // 1
在jQuery框架的私有方法和變量也是這么設計的
var $ = jQuery = function(){ return jQuery.fn.init(); } jQuery.fn = jQuery.prototype = { init:function(){ return this; //this指向jQuery.prototype }, length: 1, size: function(){ return this.length; } } console.log($().size()); // 12. 將處理結果緩存
var CachedSearchBox = (function(){ var cache = {},count = []; return { attachSearchBox : function(dsid){ if(dsid in cache){//如果結果在緩存中 return cache[dsid];//直接返回緩存中的對象 } var fsb = new uikit.webctrl.SearchBox(dsid);//新建 cache[dsid] = fsb;//更新緩存 if(count.length > 100){//保正緩存的大小<=100 delete cache[count.shift()]; } return fsb; } }; })(); CachedSearchBox.attachSearchBox("input");
這樣我們在第二次調用的時候,就會從緩存中讀取到該對象。
理解了閉包的原理我們發現閉包的這些用途都是利用了閉包保存了當前函數的活動對象的特點,這樣閉包函數在作用域之外被調用時依然能夠訪問其創建時的作用域
閉包的缺點閉包將函數的活動對象維持在內存中,過度使用閉包會導致內存占用過多,所以在使用完后需要將保存在內存中的活動對象解除引用;
閉包只能取得外部函數中任何變量的最后一個值,在使用循環且返回的函數中帶有循環變量時會得到錯誤結果;
當返回的函數為匿名函數時,注意匿名函數中的this指的是window對象。
這里僅僅是我對閉包的一些見解,若有錯誤的地方,還望大家提出,一起交流共同進步!
參考文獻
《你不知道的JavaScript》上卷
深入理解JavaScript系列
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/83917.html
摘要:當在中調用匿名函數時,它們用的都是同一個閉包,而且在這個閉包中使用了和的當前值的值為因為循環已經結束,的值為。最好將閉包當作是一個函數的入口創建的,而局部變量是被添加進這個閉包的。 閉包不是魔法 這篇文章使用一些簡單的代碼例子來解釋JavaScript閉包的概念,即使新手也可以輕松參透閉包的含義。 其實只要理解了核心概念,閉包并不是那么的難于理解。但是,網上充斥了太多學術性的文章,對于...
摘要:閉包引起的內存泄漏總結從理論的角度將由于作用域鏈的特性中所有函數都是閉包但是從應用的角度來說只有當函數以返回值返回或者當函數以參數形式使用或者當函數中自由變量在函數外被引用時才能成為明確意義上的閉包。 文章同步到github js的閉包概念幾乎是任何面試官都會問的問題,最近把閉包這塊的概念梳理了一下,記錄成以下文章。 什么是閉包 我先列出一些官方及經典書籍等書中給出的概念,這些概念雖然...
摘要:也許最好的理解是閉包總是在進入某個函數的時候被創建,而局部變量是被加入到這個閉包中。在函數內部的函數的內部聲明函數是可以的可以獲得不止一個層級的閉包。 前言 總括 :這篇文章使用有效的javascript代碼向程序員們解釋了閉包,大牛和功能型程序員請自行忽略。 譯者 :文章寫在2006年,可直到翻譯的21小時之前作者還在完善這篇文章,在Stackoverflow的How do Java...
摘要:閉包在我理解是一種比較抽象的東西。所以我寫了一篇博文來方便自己理解閉包。那么現在我們可以解釋一下閉包的第一個定義在計算機科學中,閉包是引用了自由變量的函數。循環中創建閉包在我們使用的關鍵字之前,閉包的一個常見問題就出現在循環中創建閉包。 零. 前言 從我開始接觸前端時就聽說過閉包,但是一直不理解閉包究竟是什么。上網看了各種博客,大家對閉包的說法不一。閉包在我理解是一種比較抽象的東西。所...
摘要:但是閉包也不是什么復雜到不可理解的東西,簡而言之,閉包就是閉包就是函數的局部變量集合,只是這些局部變量在函數返回后會繼續存在??上У氖?,并沒有提供相關的成員和方法來訪問閉包中的局部變量。 (收藏自 技術狂) 前言:還是一篇入門文章。Javascript中有幾個非常重要的語言特性——對象、原型繼承、閉包。其中閉包 對于那些使用傳統靜態語言C/C++的程序員來說是一個新的語言特性。本文將...
摘要:到底什么是閉包這個問題在面試是時候經常都會被問,很多小白一聽就懵逼了,不知道如何回答好。上面這么說閉包是一種特殊的對象。閉包的注意事項通常,函數的作用域及其所有變量都會在函數執行結束后被銷毀。從而使用閉包模塊化代碼,減少全局變量的污染。 閉包,有人說它是一種設計理念,有人說所有的函數都是閉包。到底什么是閉包?這個問題在面試是時候經常都會被問,很多小白一聽就懵逼了,不知道如何回答好。這個...
閱讀 2316·2021-09-22 15:27
閱讀 3170·2021-09-03 10:32
閱讀 3501·2021-09-01 11:38
閱讀 2500·2019-08-30 15:56
閱讀 2214·2019-08-30 13:01
閱讀 1537·2019-08-29 12:13
閱讀 1419·2019-08-26 13:33
閱讀 893·2019-08-26 13:30