摘要:閉包的學術定義先來參考下各大權威對閉包的學術定義百科閉包,又稱詞法閉包或函數閉包,是引用了自由變量的函數。所以,有另一種說法認為閉包是由函數和與其相關的引用環境組合而成的實體。
前言
上一章講解了閉包的底層實現細節,我想大家對閉包的概念應該也有了個大概印象,但是真要用簡短的幾句話來說清楚,這還真不是件容易的事。這里我們就來總結提煉下閉包的概念,以應付那些非專人士的心血來潮。
閉包的學術定義先來參考下各大權威對閉包的學術定義:
wiki百科閉包,又稱詞法閉包或函數閉包,是引用了自由變量的函數。這個被引用的自由變量將和這個函數一同存在,即使已經離開了創造它的環境也不例外。所以,有另一種說法認為閉包是由函數和與其相關的引用環境組合而成的實體。閉包在運行時可以有多個實例,不同的引用環境和相同的函數組合可以產生不同的實例。
其實這個定義就一句話:“閉包是引用了自由變量的函數”,后面的都是這句話的解釋。如果你對上一章中的內部函數作用域鏈有引用type變量的例子還有印象的話,那么在這里你會感覺好像是這么一會一回事。雖然我們不知道自由變量的明確定義,但我們能感覺到type的值就是這個自由變量。
那究竟什么是自由變量?在一個作用域中使用某個變量,而不聲明該變量,那么對這個作用域來說,該變量就是一個自由變量。
函數對象可以通過作用域鏈相互關聯起來,函數體內部的變量都可以保存在函數作用域內,這種特性在計算機科學文獻中稱為閉包。
這句話有一個關鍵詞:“變量保存”,這確實是js閉包的一大特性。內部函數通過對自由變量的引用,再將自己的引用返回出去(內部函數),達到內部函數保存變量的效果。
JavaScript 高級程序設計閉包是指有權訪問另一個函數作用域中的變量的函數。
這里沒有指明另一個函數就是嵌套函數的內部函數。事實上,在js中,只有內部函數有權訪問外部函數作用域的變量。(這是由作用域鏈的查找機制決定的)
讓我再結合上節的例子來看下:
function isType(type){ return function(obj){ //返回一個匿名函數引用 return Object.prototype.toString.call(obj) == "[object "+ type + "]"; //匿名函數內部保有對自由變量的type的引用 } } var isFunction = isType("Function"); //匿名函數的引用數 1 var isString = isType("String"); //匿名函數的引用數 2 //測試 var name = "Tom"; isString(name)//true我對閉包的理解
如果 一個內部函數保有對外部作用域變量的引用 并且 這個內部函數也被引用 時,那么無論在什么執行環境下,這個被引用的變量將和這個函數一同存在。那個這個函數就是閉包。
js 閉包技巧 閉包引用帶來的問題下面我來看一道關于閉包的經典面試題,1秒后打印所有的迭代次數。通常我們可能會寫出下面這樣的代碼:
function timeCount() { for (var i = 1; i < 5; i++) { setTimeout(function(){ console.log(i) },1000) } } timeCount(); //5 5 5 5 5
事實上這個例子,并不是關于閉包的技巧,相反它是由閉包特性帶來的問題。理解這個問題有助于我們理解閉包。首先我們來看導致這個問題的原因:
1.setTimeout為異步任務;
2.回調函數中的i只有一個引用;
異步任務意味著它并不會馬上執行,而是被推到一個異步任務隊列中等待執行,直到js線程任務執行完后才會去執行這個隊列中的任務。(類似的異步任務還有dom的交互事件綁定)
也就是說,當每次執行循環體的setTimeout方法時,js執行器并沒有馬上執行而是將其推入異步任務隊列中。當5次循環執行完后,js線程再去執行異步隊列中的任務(此時的i就是5了)。
解決的方法也很簡單,那就是不使用i的引用,直接使用i的副本。那怎么使用i的副本?
《JavaScript高級程序設計》中提到,所有函數的參數都是按值傳遞的,什么意思?比如有一個函數 function add(num){},當我調用這個函數時 add(i), 在add函數內部變量i 不再是外部函數i的引用,而是一個獨立存在的 與i的值相等的變量。這也就達到了復制i的作用。
(function(){})()是匿名函數的自執行寫法。
function timeCount() { for (var i = 1; i < 5; i++) { (function(i){ setTimeout(function(){ console.log(i) },1000) })(i) } } timeCount(); //1 2 3 4 5
有閉包的bug一般都比較隱匿,這會增加調試的難度。這也就是為什么很多老手都不推薦大量使用閉包的原因之一,還有一個就是不釋放變量的內存空間。
模擬私有成員在JavaScript中是沒有私有成員的概念,不能使用private關鍵字聲明,所有的屬性都是公有的。所以人們在JavaScript編程通常用兩種方法來規定私有成員:
1.私有成員以下劃線的方式命名;
2.利用閉包來模擬私有成員;
第一種方法是最簡單的,而且效果還可以的方法,它的實現完全靠程序員的自覺性,很難保證不會有人刻意去使用私有成員。第二種方法雖然有點難理解,但它確實有效地實現了私有成員的保護。雖然js沒有私有成員的概念,但是函數有私有變量的概念,函數外部不能訪問私有變量。所以我們可以利用閉包的特性,創建訪問私有變量的公有方法(特權方法)。
function Person(value) { var name = value; this.setName = function(newName) { name = newName; }; this.getName= function() { return name; }; } var tom = new Person("Tom"); console.log(tom.getName()); // Tom
利用閉包,我們可以通過特權方法來獲取和修改私有變量,從而達到約束和規范代碼的作用,這在大型應用開發中尤為重要。但是這種寫法還需要改進,我們希望實例能夠共享實例方法,而不是通過復制來得到這些方法的使用:
(function() { var name; Person = function(value){ //不聲明變量person,使其可以在全局被訪問 name = value; }; Person.prototype = { setName: function (newName) { name = newName; }, getName: function() { return name; } } })() var tom = new Person("Tom"); console.log(tom.getName()); // Tom
創建一個匿名自執行函數,是為了得到一個靜態私有作用域,在這個靜態作用域中創建的name變量,這樣既可以保證它的數據安全,也能被實例方法所訪問。
函數的柯里化柯里化是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,并且返回接受余下的參數而且返回結果的新函數的技術。---來自wiki百科
這里有一點要注意,單一參數并不是指一個參數,而是最初函數的參數(外部函數),可以是多個。
它的理論依據是,如果你固定某些參數,你將得到接受余下參數的一個函數。這很容易理解,如果我們有一個二元一次方程,z = x + y;我固定x的值為3,這個方程就變成了一元一次方程,z = 3 + y;
柯里化的過程是清楚了,但是他的目的是什么呢,我們為什么要固定一個參數返回一個新函數?為什么不直接定義一個新函數呢?
如果直接定義一個新函數,原來的參數變成函數內部固定的私有變量,這樣一來雖然特定的功能完成了,但是代碼的通用性卻降低了。基于這個應用場景創造的新函數,換了一個相似的應用場景(只是參數的改變)卻不得不重新定義一個新函數,造成了代碼的重復。
通用性的增強必然導致適用性的降低,柯里化就是這么一個過程,將原本接受多個參數的函數(因為多個參數,自然適應的業務場景就多,通用性也就強),轉為接受少個參數的新函數(參數少,應用的場景也就更明確,適用性也就強)。 這么一來,通過柯里化,開發者便可掌握代碼的通用性和適用性之間的平衡。
單例模式這個理解起來可能有點吃力,畢竟柯里化是屬于函數式編程里的重要技巧,一般像我們這種習慣面向對象開發的人確實會比較難以領會它的精髓。
單例模式的定義是產生一個類的唯一實例,很多js的開發者認為,類似Java那種單例模式的創造方式在JavaScript中沒有必要。因為在js中,不需要實例化也可創建對象,只要直接全局作用域創建一個字面量對象,以便整個系統訪問。
單例模式在js中的應用場景確實也不算多,主要應用在框架層,而大多數js的開發者是從事應用層的開發,所以接觸不多。比如一個遮罩層的創建,為確保一次只有一個遮罩層,使用單例模式是最好的選擇。
var singleton = function( fn ){ var result; return function(){ return result || ( result = fn .apply( this, arguments ) ); } } var createMask = singleton( function(){ return document.body.appendChild( document.createElement("div") ); } )函數綁定
這一個技巧放在最后講,是因為ES5規定了對原生函數綁定方法的實現——Function.prototype.bind。使用閉包來綁定this變量的hack技術已經退出歷史舞臺,但是老版的IE瀏覽器依然在使用這種技術來實現函數的綁定。
先來看一個場景
var tip = { name: "jack", say: function() { alert(this.name) } } btn.onclick = tip.say(); // 輸出 "",因為window對象存在name屬性,是一個空字符串
在注冊事件中的事件處理程序沒有綁定執行環境,所以當觸發事件處理程序時,this指向正在執行的環境對象,在這里是全局對象window。最常見的解決方法就是綁定他的執行環境對象
btn.onclick = tip.say().bind(tip); // jack
還有一種方法,就是利用apply+閉包來達到綁定效果,apply將事件處理程序與正確的環境對象綁定,再將綁定后的函數返回賦值給事件處理程序。它常用作不支持原生bind方法的兼容性處理。
if (!Function.prototype.bind) { Function.prototype.bind = function (oThis) { if (typeof this !== "function") { // closest thing possible to the ECMAScript 5 // internal IsCallable function throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable"); } var aArgs = Array.prototype.slice.call(arguments, 1), fToBind = this, fNOP = function () {}, fBound = function () { return fToBind.apply(this instanceof fNOP ? this : oThis || this, aArgs.concat(Array.prototype.slice.call(arguments))); }; fNOP.prototype = this.prototype; fBound.prototype = new fNOP(); return fBound; }; }
調用方式與原生bind相同。
總結閉包的技巧就介紹到這,更多的技巧還需要我們去開發中發現、領會并運用。下一章,我們來聊一聊js中最強大的屬性之一——prototype。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/86522.html
摘要:大名鼎鼎的閉包面試必問。閉包的作用是什么。看到閉包在哪了嗎閉包到底是什么五年前,我也被這個問題困擾,于是去搜了并總結下來。關于閉包的謠言閉包會造成內存泄露錯。閉包里面的變量明明就是我們需要的變量,憑什么說是內存泄露這個謠言是如何來的因為。 本文為饑人谷講師方方原創文章,首發于 前端學習指南。 大名鼎鼎的閉包!面試必問。請用自己的話簡述 什么是「閉包」。 「閉包」的作用是什么。 首先...
摘要:內部的稱為內部函數或閉包函數。過度使用閉包會導致性能下降。,閉包函數分為定義時,和運行時。循環會先運行完畢,此時,閉包函數并沒有運行。閉包只能取得外部函數中的最后一個值。事件綁定種的匿名函數也是閉包函數。而對象中的閉包函數,指向。 閉包概念解釋: 閉包(也叫詞法閉包或者函數閉包)。 在一個函數parent內聲明另一個函數child,形成了嵌套。函數child使用了函數parent的參數...
摘要:一般來講,函數執行完畢后,局部活動對象就會被銷毀,內存中僅保存全局作用域,但是閉包的情況有所不同理解閉包的前提先理解另外兩個內容作用域鏈垃圾回收作用域鏈當代碼在執行過程中,會創建變量對象的一個作用域鏈。 閉包是javascript語言的一個難點,也是它的特色,很多高級應用都要依靠閉包來實現。個人的理解是:函數中嵌套函數。 閉包的定義及其優缺點 閉包是指有權訪問另一個函數作用域中的變量的...
摘要:當初看這個解釋有點懵逼,理解成閉包就是函數中的函數了。里的閉包最近不滿足于只干前端的活,開始用起了。里的閉包最近在學習語言,讓我們來看一下語言里的閉包。在中,閉包特指將函數作為值返回的情況,被返回的函數引用了生成它的母函數中的變量。 本人開始接觸編程是從js開始的,當時網上很多人說閉包是難點,各種地方對閉包的解釋也是千奇百怪。如今開始接觸js以外的各種編程語言,發現不光是js,php、...
閱讀 2169·2021-11-11 16:55
閱讀 1692·2019-08-30 15:54
閱讀 2822·2019-08-30 15:53
閱讀 2216·2019-08-30 15:44
閱讀 1156·2019-08-30 15:43
閱讀 970·2019-08-30 11:22
閱讀 1947·2019-08-29 17:20
閱讀 1572·2019-08-29 16:56