摘要:閉包的學(xué)術(shù)定義先來參考下各大權(quán)威對(duì)閉包的學(xué)術(shù)定義百科閉包,又稱詞法閉包或函數(shù)閉包,是引用了自由變量的函數(shù)。所以,有另一種說法認(rèn)為閉包是由函數(shù)和與其相關(guān)的引用環(huán)境組合而成的實(shí)體。
前言
上一章講解了閉包的底層實(shí)現(xiàn)細(xì)節(jié),我想大家對(duì)閉包的概念應(yīng)該也有了個(gè)大概印象,但是真要用簡(jiǎn)短的幾句話來說清楚,這還真不是件容易的事。這里我們就來總結(jié)提煉下閉包的概念,以應(yīng)付那些非專人士的心血來潮。
閉包的學(xué)術(shù)定義先來參考下各大權(quán)威對(duì)閉包的學(xué)術(shù)定義:
wiki百科閉包,又稱詞法閉包或函數(shù)閉包,是引用了自由變量的函數(shù)。這個(gè)被引用的自由變量將和這個(gè)函數(shù)一同存在,即使已經(jīng)離開了創(chuàng)造它的環(huán)境也不例外。所以,有另一種說法認(rèn)為閉包是由函數(shù)和與其相關(guān)的引用環(huán)境組合而成的實(shí)體。閉包在運(yùn)行時(shí)可以有多個(gè)實(shí)例,不同的引用環(huán)境和相同的函數(shù)組合可以產(chǎn)生不同的實(shí)例。
其實(shí)這個(gè)定義就一句話:“閉包是引用了自由變量的函數(shù)”,后面的都是這句話的解釋。如果你對(duì)上一章中的內(nèi)部函數(shù)作用域鏈有引用type變量的例子還有印象的話,那么在這里你會(huì)感覺好像是這么一會(huì)一回事。雖然我們不知道自由變量的明確定義,但我們能感覺到type的值就是這個(gè)自由變量。
那究竟什么是自由變量?在一個(gè)作用域中使用某個(gè)變量,而不聲明該變量,那么對(duì)這個(gè)作用域來說,該變量就是一個(gè)自由變量。
函數(shù)對(duì)象可以通過作用域鏈相互關(guān)聯(lián)起來,函數(shù)體內(nèi)部的變量都可以保存在函數(shù)作用域內(nèi),這種特性在計(jì)算機(jī)科學(xué)文獻(xiàn)中稱為閉包。
這句話有一個(gè)關(guān)鍵詞:“變量保存”,這確實(shí)是js閉包的一大特性。內(nèi)部函數(shù)通過對(duì)自由變量的引用,再將自己的引用返回出去(內(nèi)部函數(shù)),達(dá)到內(nèi)部函數(shù)保存變量的效果。
JavaScript 高級(jí)程序設(shè)計(jì)閉包是指有權(quán)訪問另一個(gè)函數(shù)作用域中的變量的函數(shù)。
這里沒有指明另一個(gè)函數(shù)就是嵌套函數(shù)的內(nèi)部函數(shù)。事實(shí)上,在js中,只有內(nèi)部函數(shù)有權(quán)訪問外部函數(shù)作用域的變量。(這是由作用域鏈的查找機(jī)制決定的)
讓我再結(jié)合上節(jié)的例子來看下:
function isType(type){ return function(obj){ //返回一個(gè)匿名函數(shù)引用 return Object.prototype.toString.call(obj) == "[object "+ type + "]"; //匿名函數(shù)內(nèi)部保有對(duì)自由變量的type的引用 } } var isFunction = isType("Function"); //匿名函數(shù)的引用數(shù) 1 var isString = isType("String"); //匿名函數(shù)的引用數(shù) 2 //測(cè)試 var name = "Tom"; isString(name)//true我對(duì)閉包的理解
如果 一個(gè)內(nèi)部函數(shù)保有對(duì)外部作用域變量的引用 并且 這個(gè)內(nèi)部函數(shù)也被引用 時(shí),那么無論在什么執(zhí)行環(huán)境下,這個(gè)被引用的變量將和這個(gè)函數(shù)一同存在。那個(gè)這個(gè)函數(shù)就是閉包。
js 閉包技巧 閉包引用帶來的問題下面我來看一道關(guān)于閉包的經(jīng)典面試題,1秒后打印所有的迭代次數(shù)。通常我們可能會(huì)寫出下面這樣的代碼:
function timeCount() { for (var i = 1; i < 5; i++) { setTimeout(function(){ console.log(i) },1000) } } timeCount(); //5 5 5 5 5
事實(shí)上這個(gè)例子,并不是關(guān)于閉包的技巧,相反它是由閉包特性帶來的問題。理解這個(gè)問題有助于我們理解閉包。首先我們來看導(dǎo)致這個(gè)問題的原因:
1.setTimeout為異步任務(wù);
2.回調(diào)函數(shù)中的i只有一個(gè)引用;
異步任務(wù)意味著它并不會(huì)馬上執(zhí)行,而是被推到一個(gè)異步任務(wù)隊(duì)列中等待執(zhí)行,直到j(luò)s線程任務(wù)執(zhí)行完后才會(huì)去執(zhí)行這個(gè)隊(duì)列中的任務(wù)。(類似的異步任務(wù)還有dom的交互事件綁定)
也就是說,當(dāng)每次執(zhí)行循環(huán)體的setTimeout方法時(shí),js執(zhí)行器并沒有馬上執(zhí)行而是將其推入異步任務(wù)隊(duì)列中。當(dāng)5次循環(huán)執(zhí)行完后,js線程再去執(zhí)行異步隊(duì)列中的任務(wù)(此時(shí)的i就是5了)。
解決的方法也很簡(jiǎn)單,那就是不使用i的引用,直接使用i的副本。那怎么使用i的副本?
《JavaScript高級(jí)程序設(shè)計(jì)》中提到,所有函數(shù)的參數(shù)都是按值傳遞的,什么意思?比如有一個(gè)函數(shù) function add(num){},當(dāng)我調(diào)用這個(gè)函數(shù)時(shí) add(i), 在add函數(shù)內(nèi)部變量i 不再是外部函數(shù)i的引用,而是一個(gè)獨(dú)立存在的 與i的值相等的變量。這也就達(dá)到了復(fù)制i的作用。
(function(){})()是匿名函數(shù)的自執(zhí)行寫法。
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一般都比較隱匿,這會(huì)增加調(diào)試的難度。這也就是為什么很多老手都不推薦大量使用閉包的原因之一,還有一個(gè)就是不釋放變量的內(nèi)存空間。
模擬私有成員在JavaScript中是沒有私有成員的概念,不能使用private關(guān)鍵字聲明,所有的屬性都是公有的。所以人們?cè)贘avaScript編程通常用兩種方法來規(guī)定私有成員:
1.私有成員以下劃線的方式命名;
2.利用閉包來模擬私有成員;
第一種方法是最簡(jiǎn)單的,而且效果還可以的方法,它的實(shí)現(xiàn)完全靠程序員的自覺性,很難保證不會(huì)有人刻意去使用私有成員。第二種方法雖然有點(diǎn)難理解,但它確實(shí)有效地實(shí)現(xiàn)了私有成員的保護(hù)。雖然js沒有私有成員的概念,但是函數(shù)有私有變量的概念,函數(shù)外部不能訪問私有變量。所以我們可以利用閉包的特性,創(chuàng)建訪問私有變量的公有方法(特權(quán)方法)。
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
利用閉包,我們可以通過特權(quán)方法來獲取和修改私有變量,從而達(dá)到約束和規(guī)范代碼的作用,這在大型應(yīng)用開發(fā)中尤為重要。但是這種寫法還需要改進(jìn),我們希望實(shí)例能夠共享實(shí)例方法,而不是通過復(fù)制來得到這些方法的使用:
(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
創(chuàng)建一個(gè)匿名自執(zhí)行函數(shù),是為了得到一個(gè)靜態(tài)私有作用域,在這個(gè)靜態(tài)作用域中創(chuàng)建的name變量,這樣既可以保證它的數(shù)據(jù)安全,也能被實(shí)例方法所訪問。
函數(shù)的柯里化柯里化是把接受多個(gè)參數(shù)的函數(shù)變換成接受一個(gè)單一參數(shù)(最初函數(shù)的第一個(gè)參數(shù))的函數(shù),并且返回接受余下的參數(shù)而且返回結(jié)果的新函數(shù)的技術(shù)。---來自wiki百科
這里有一點(diǎn)要注意,單一參數(shù)并不是指一個(gè)參數(shù),而是最初函數(shù)的參數(shù)(外部函數(shù)),可以是多個(gè)。
它的理論依據(jù)是,如果你固定某些參數(shù),你將得到接受余下參數(shù)的一個(gè)函數(shù)。這很容易理解,如果我們有一個(gè)二元一次方程,z = x + y;我固定x的值為3,這個(gè)方程就變成了一元一次方程,z = 3 + y;
柯里化的過程是清楚了,但是他的目的是什么呢,我們?yōu)槭裁匆潭ㄒ粋€(gè)參數(shù)返回一個(gè)新函數(shù)?為什么不直接定義一個(gè)新函數(shù)呢?
如果直接定義一個(gè)新函數(shù),原來的參數(shù)變成函數(shù)內(nèi)部固定的私有變量,這樣一來雖然特定的功能完成了,但是代碼的通用性卻降低了?;谶@個(gè)應(yīng)用場(chǎng)景創(chuàng)造的新函數(shù),換了一個(gè)相似的應(yīng)用場(chǎng)景(只是參數(shù)的改變)卻不得不重新定義一個(gè)新函數(shù),造成了代碼的重復(fù)。
通用性的增強(qiáng)必然導(dǎo)致適用性的降低,柯里化就是這么一個(gè)過程,將原本接受多個(gè)參數(shù)的函數(shù)(因?yàn)槎鄠€(gè)參數(shù),自然適應(yīng)的業(yè)務(wù)場(chǎng)景就多,通用性也就強(qiáng)),轉(zhuǎn)為接受少個(gè)參數(shù)的新函數(shù)(參數(shù)少,應(yīng)用的場(chǎng)景也就更明確,適用性也就強(qiáng))。 這么一來,通過柯里化,開發(fā)者便可掌握代碼的通用性和適用性之間的平衡。
單例模式這個(gè)理解起來可能有點(diǎn)吃力,畢竟柯里化是屬于函數(shù)式編程里的重要技巧,一般像我們這種習(xí)慣面向?qū)ο箝_發(fā)的人確實(shí)會(huì)比較難以領(lǐng)會(huì)它的精髓。
單例模式的定義是產(chǎn)生一個(gè)類的唯一實(shí)例,很多js的開發(fā)者認(rèn)為,類似Java那種單例模式的創(chuàng)造方式在JavaScript中沒有必要。因?yàn)樵趈s中,不需要實(shí)例化也可創(chuàng)建對(duì)象,只要直接全局作用域創(chuàng)建一個(gè)字面量對(duì)象,以便整個(gè)系統(tǒng)訪問。
單例模式在js中的應(yīng)用場(chǎng)景確實(shí)也不算多,主要應(yīng)用在框架層,而大多數(shù)js的開發(fā)者是從事應(yīng)用層的開發(fā),所以接觸不多。比如一個(gè)遮罩層的創(chuàng)建,為確保一次只有一個(gè)遮罩層,使用單例模式是最好的選擇。
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") ); } )函數(shù)綁定
這一個(gè)技巧放在最后講,是因?yàn)镋S5規(guī)定了對(duì)原生函數(shù)綁定方法的實(shí)現(xiàn)——Function.prototype.bind。使用閉包來綁定this變量的hack技術(shù)已經(jīng)退出歷史舞臺(tái),但是老版的IE瀏覽器依然在使用這種技術(shù)來實(shí)現(xiàn)函數(shù)的綁定。
先來看一個(gè)場(chǎng)景
var tip = { name: "jack", say: function() { alert(this.name) } } btn.onclick = tip.say(); // 輸出 "",因?yàn)閣indow對(duì)象存在name屬性,是一個(gè)空字符串
在注冊(cè)事件中的事件處理程序沒有綁定執(zhí)行環(huán)境,所以當(dāng)觸發(fā)事件處理程序時(shí),this指向正在執(zhí)行的環(huán)境對(duì)象,在這里是全局對(duì)象window。最常見的解決方法就是綁定他的執(zhí)行環(huán)境對(duì)象
btn.onclick = tip.say().bind(tip); // jack
還有一種方法,就是利用apply+閉包來達(dá)到綁定效果,apply將事件處理程序與正確的環(huán)境對(duì)象綁定,再將綁定后的函數(shù)返回賦值給事件處理程序。它常用作不支持原生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; }; }
調(diào)用方式與原生bind相同。
總結(jié)閉包的技巧就介紹到這,更多的技巧還需要我們?nèi)ラ_發(fā)中發(fā)現(xiàn)、領(lǐng)會(huì)并運(yùn)用。下一章,我們來聊一聊js中最強(qiáng)大的屬性之一——prototype。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/86522.html
摘要:大名鼎鼎的閉包面試必問。閉包的作用是什么??吹介]包在哪了嗎閉包到底是什么五年前,我也被這個(gè)問題困擾,于是去搜了并總結(jié)下來。關(guān)于閉包的謠言閉包會(huì)造成內(nèi)存泄露錯(cuò)。閉包里面的變量明明就是我們需要的變量,憑什么說是內(nèi)存泄露這個(gè)謠言是如何來的因?yàn)椤? 本文為饑人谷講師方方原創(chuàng)文章,首發(fā)于 前端學(xué)習(xí)指南。 大名鼎鼎的閉包!面試必問。請(qǐng)用自己的話簡(jiǎn)述 什么是「閉包」。 「閉包」的作用是什么。 首先...
摘要:也正因?yàn)檫@個(gè)閉包的特性,閉包函數(shù)可以讓父函數(shù)的數(shù)據(jù)一直駐留在內(nèi)存中保存,從而這也是后來模塊化的基礎(chǔ)。只有閉包函數(shù),可以讓它的父函數(shù)作用域永恒,像全局作用域,一直在內(nèi)存中存在。的本質(zhì)就是如此,每個(gè)模塊文件就是一個(gè)大閉包。 為什么會(huì)有閉包 js之所以會(huì)有閉包,是因?yàn)閖s不同于其他規(guī)范的語言,js允許一個(gè)函數(shù)中再嵌套子函數(shù),正是因?yàn)檫@種允許函數(shù)嵌套,導(dǎo)致js出現(xiàn)了所謂閉包。 function...
摘要:但閉包的情況不同嵌套函數(shù)的閉包執(zhí)行后,,然后還在被回收閉包會(huì)使變量始終保存在內(nèi)存中,如果不當(dāng)使用會(huì)增大內(nèi)存消耗。每個(gè)函數(shù),不論多深,都可以認(rèn)為是全局的子作用域,可以理解為閉包。 閉包(closure)是Javascript語言的一個(gè)難點(diǎn),也是它的特色,很多高級(jí)應(yīng)用都要依靠閉包實(shí)現(xiàn)。 閉包的特性 閉包有三個(gè)特性: 1.函數(shù)嵌套函數(shù) 2.函數(shù)內(nèi)部可以引用外部的參數(shù)和變量 3.參數(shù)和變量不會(huì)...
摘要:內(nèi)部的稱為內(nèi)部函數(shù)或閉包函數(shù)。過度使用閉包會(huì)導(dǎo)致性能下降。,閉包函數(shù)分為定義時(shí),和運(yùn)行時(shí)。循環(huán)會(huì)先運(yùn)行完畢,此時(shí),閉包函數(shù)并沒有運(yùn)行。閉包只能取得外部函數(shù)中的最后一個(gè)值。事件綁定種的匿名函數(shù)也是閉包函數(shù)。而對(duì)象中的閉包函數(shù),指向。 閉包概念解釋: 閉包(也叫詞法閉包或者函數(shù)閉包)。 在一個(gè)函數(shù)parent內(nèi)聲明另一個(gè)函數(shù)child,形成了嵌套。函數(shù)child使用了函數(shù)parent的參數(shù)...
摘要:一般來講,函數(shù)執(zhí)行完畢后,局部活動(dòng)對(duì)象就會(huì)被銷毀,內(nèi)存中僅保存全局作用域,但是閉包的情況有所不同理解閉包的前提先理解另外兩個(gè)內(nèi)容作用域鏈垃圾回收作用域鏈當(dāng)代碼在執(zhí)行過程中,會(huì)創(chuàng)建變量對(duì)象的一個(gè)作用域鏈。 閉包是javascript語言的一個(gè)難點(diǎn),也是它的特色,很多高級(jí)應(yīng)用都要依靠閉包來實(shí)現(xiàn)。個(gè)人的理解是:函數(shù)中嵌套函數(shù)。 閉包的定義及其優(yōu)缺點(diǎn) 閉包是指有權(quán)訪問另一個(gè)函數(shù)作用域中的變量的...
摘要:當(dāng)初看這個(gè)解釋有點(diǎn)懵逼,理解成閉包就是函數(shù)中的函數(shù)了。里的閉包最近不滿足于只干前端的活,開始用起了。里的閉包最近在學(xué)習(xí)語言,讓我們來看一下語言里的閉包。在中,閉包特指將函數(shù)作為值返回的情況,被返回的函數(shù)引用了生成它的母函數(shù)中的變量。 本人開始接觸編程是從js開始的,當(dāng)時(shí)網(wǎng)上很多人說閉包是難點(diǎn),各種地方對(duì)閉包的解釋也是千奇百怪。如今開始接觸js以外的各種編程語言,發(fā)現(xiàn)不光是js,php、...
閱讀 2226·2021-11-11 16:55
閱讀 1744·2019-08-30 15:54
閱讀 2882·2019-08-30 15:53
閱讀 2275·2019-08-30 15:44
閱讀 1212·2019-08-30 15:43
閱讀 1014·2019-08-30 11:22
閱讀 2015·2019-08-29 17:20
閱讀 1618·2019-08-29 16:56