摘要:閉包閉包是指有權(quán)訪問另一個函數(shù)作用域中的變量的函數(shù)。創(chuàng)建閉包的常見方式,就是在一個函數(shù)內(nèi)部創(chuàng)建另一個函數(shù)。在這里產(chǎn)生了一個閉包結(jié)構(gòu),局部變量的生命周期被延續(xù)了。本節(jié)內(nèi)容為設(shè)計模式與開發(fā)實踐第三章筆記。
閉包
閉包是指有權(quán)訪問另一個函數(shù)作用域中的變量的函數(shù)。
創(chuàng)建閉包的常見方式,就是在一個函數(shù)內(nèi)部創(chuàng)建另一個函數(shù)。閉包的形成與變量的作用域以及變量的生存周期有關(guān)。
變量的作用域變量的作用域就是指變量的有效范圍。
當在函數(shù)中聲明一個變量時,如果變量前面沒有帶上關(guān)鍵字var,這個變量就會成為全局變量;如果用var關(guān)鍵字在函數(shù)中聲明變量,這個變量就是局部變量,只有在該函數(shù)內(nèi)部才能訪問到這個變量,在函數(shù)外部是訪問不到的。
在JavaScript中,函數(shù)可以用來創(chuàng)造函數(shù)作用域。在函數(shù)里面可以看到外面的變量,而在函數(shù)外面則無法看到函數(shù)里面的變量。這是因為當在函數(shù)中搜索一個變量的時候,如果該函數(shù)內(nèi)并沒有聲明這個變量,那么此次搜索的過程會隨著代碼的執(zhí)行環(huán)境創(chuàng)建的作用域鏈往外層逐層搜索,一直搜索到全局對象。變量的搜索是從內(nèi)到外的。
var a = 1; var func1 = function(){ var b = 2; var func2 = function(){ var c = 3; console.log(b); // 輸出:2 console.log(c); // 輸出:1 } func2(); console.log(c); // 變量c在函數(shù)內(nèi)部,是局部變量,此時在外部訪問不到。 輸出:Uncaught ReferenceError: c is not defined }; func1();變量的生存周期
全局變量的生存周期是永久的,除非我們主動銷毀這個全局變量。而在函數(shù)內(nèi)用var關(guān)鍵字聲明的局部變量,當退出函數(shù)時,這些局部變量即失去了它們的價值,會隨著函數(shù)調(diào)用的結(jié)束而被銷毀:
var func = function(){ var a = 1; // 退出函數(shù)后局部變量a將被銷毀 console.log(a); // 輸出:1 }; func();
但是,有一種情況卻跟我們的推論相反。
var func = function(){ var a = 1; //函數(shù)外部訪問不到局部變量a,退出函數(shù)后,局部變量a被銷毀 console.log(a); // 輸出:1 }; func(); console.log(a); // 輸出:Uncaught ReferenceError: a is not defined var func = function(){ var a = 1; return function(){ a++; console.log(a); } }; var f = func(); f(); // 輸出:2 f(); // 輸出:3 f(); // 輸出:4 f(); // 輸出:5
當退出函數(shù)后,局部變量a并沒有消失,而是似乎一直在某個地方存活著。這是因為當執(zhí)行 var f = func(); 時,f返回了一個匿名函數(shù)的引用,它可以訪問到func()被調(diào)用時產(chǎn)生的環(huán)境,而布局變量a一直處在這個環(huán)境里。既然局部變量所在的環(huán)境還能被外界訪問,這個局部變量就有了不被銷毀的理由。在這里產(chǎn)生了一個閉包結(jié)構(gòu),局部變量的生命周期被延續(xù)了。
閉包的作用封裝變量
延續(xù)局部變量的壽命
1. 封裝變量
閉包可以幫助把一些不需要暴露在全局的變量封裝成“私有變量”。
假設(shè)有一個計算乘積的函數(shù):
var cache = {}; var mult = function(){ var args = Array.prototype.join.call(arguments, ","); if(cache[args]){ return cache[args]; } var a = 1; for(var i=0, l=arguments.length; i< l; i++){ a = a * arguments[i]; } return cache[args] = a; }; console.log(mult(1,2,3)); // 輸出:6 console.log(mult(1,2,3)); // 輸出:6
我們看到cache這個變量僅僅在mult函數(shù)中被使用,與其讓cache變量跟mult函數(shù)一起平行地暴露在全局作用域下,不如把它封閉在mult函數(shù)內(nèi)部,這樣可以減少頁面中的全局變量,以避免這個變量在其他地方被不小心修改而引發(fā)錯誤。
var mult = (function(){ var cache = {}; return function(){ var args = Array.prototype.join.call(arguments, ","); if(args in cache){ return cache[args]; } var a = 1; for(var i=0, l=arguments.length; i提煉函數(shù)是代碼重構(gòu)中的一種常見技巧。如果在一個大函數(shù)中有一些代碼能夠獨立出來,就把這些代碼封裝在獨立的小函數(shù)里。獨立出來的小函數(shù)有助于代碼服用。
var mult = (function(){ var cache = {}; var calculate = function(){ var a = 1; for(var i=0, l=arguments.length; i2.延續(xù)局部變量的壽命
img對象常用于進行數(shù)據(jù)上報,如下:var report = function(src) { var img = new Image(); img.src = src; }; report("http://xxx.com/getUserInfo");一些低版本瀏覽器的實現(xiàn)存在bug,在這些瀏覽器中使用report函數(shù)進行數(shù)據(jù)上報會丟失30%左右的數(shù)據(jù),也就是說,report函數(shù)并不是每一次都成功發(fā)起了HTTP請求。丟失數(shù)據(jù)的原因是img是report函數(shù)中的局部變量,當report函數(shù)的調(diào)用結(jié)束后,img局部變量隨即被銷毀,而此時或許還沒來得及發(fā)出HTTP請求,所以此次請求就會丟失掉。
把img變量用閉包封閉起來:
var report =(function(){ var imgs = []; return function(src) { var img = new Image(); imgs.push(img); img.src = src; } })();閉包和面向?qū)ο笤O(shè)計過程與數(shù)據(jù)的結(jié)合是形容面向?qū)ο笾械摹皩ο蟆睍r經(jīng)常使用的表達。對象以方法的形式包含了過程,而閉包則是在過程中以環(huán)境的形式包含了數(shù)據(jù)。通常用面對對象思想能實現(xiàn)的功能,用閉包也能實現(xiàn),反之亦然。
看看這段面向?qū)ο髮懛ǖ拇a:
var extent = { value: 0; call: function(){ this.value++; console.log(this.value); } }; // 作為對象的方法調(diào)用,this指向該對象 extent.call(); // 輸出:1 extent.call(); // 輸出:2 extent.call(); // 輸出:3換成閉包的寫法如下:
var extent = function(){ var value = 0; return { call: function(){ value++; console.log(value); } } }; var extent = extent(); extent.call(); // 輸出:1 extent.call(); // 輸出:2 extent.call(); // 輸出:3閉包與內(nèi)存管理局部變量本來應該在函數(shù)退出的時候就被解除引用,但如果局部變量被封閉在閉包形成的環(huán)境中,那么這個局部變量就能一直生存下去。從這個意義上看,閉包確實會使一些數(shù)據(jù)無法被及時銷毀。使用閉包的一部分原因是我們選擇主動把一些變量封閉在閉包中,因為可能在以后還需要使用這些變量,把這些對象放在閉包中和放在全局作用域中,對內(nèi)存方面的影響是一致的。如果在將來需要回收這些變量,可以手動把變量設(shè)為null。
使用閉包的同時比較容易造成循環(huán)引用,如果閉包的作用域鏈中保存著一些DOM節(jié)點,這時候就有可能造成內(nèi)存泄露。但這本身并非閉包的問題,也并非JavaScript的問題。在IE瀏覽器中,由于BOM和DOM中的對象是使用C++以COM對象的方式實現(xiàn)的,而COM對象的垃圾收集機制采用的是引用計數(shù)策略。在基于引用計數(shù)策略的垃圾回收機制中,如果兩個對象之間形成了循環(huán)引用,那么這兩個對象都無法被回收,但循環(huán)引用造成的內(nèi)存泄露在本質(zhì)上也不是閉包造成的。
如果要解決循環(huán)引用帶來的內(nèi)存泄露問題,我們只需要把循環(huán)引用中的變量設(shè)為null。將變量設(shè)為null,意味著切斷變量與它之前引用的值之間的連接。當垃圾收集器下次運行時,就會刪除這些值并回收它們占用的內(nèi)存。
高階函數(shù) 定義高階函數(shù)是指至少滿足下列條件之一的函數(shù):
函數(shù)可以作為參數(shù)被傳遞;
函數(shù)可以作為返回值輸出。
函數(shù)作為參數(shù)傳遞1. 回調(diào)函數(shù)
在ajax異步請求的應用中,回調(diào)函數(shù)的使用非常頻繁。當我們想在ajax請求返回之后做一些事情,但又不知道請求返回的確切時間時,最常見的方案就是把callback函數(shù)當作參數(shù)傳入發(fā)起的ajax請求的方法中,待請求完成之后執(zhí)行callback函數(shù):
var getUserInfo = function(){ $.ajax("http://xxx.com/getUserInfo?" + userId, function(data){ if(typeof callback === "function"){ callback(data); } }); } getUserInfo(13157, function(data){ console.log(data.userName); });回調(diào)函數(shù)的應用不僅只在異步請求中,當一個函數(shù)不適合執(zhí)行一些請求時,我們也可以把這些請求封裝成一個函數(shù),并把它作為參數(shù)傳遞給另一個函數(shù),“委托”給另一個函數(shù)來執(zhí)行。
2. Array.prototype.sort
Array.prototype.sort接受一個函數(shù)當作參數(shù),這個函數(shù)里面封裝了數(shù)組元素的排序規(guī)則。從Array.prototype.sort的使用可以看到,我們的目的是對數(shù)組進行排序,這是不變的部分;而使用什么規(guī)則去排序,則是可變的部分。把可變的部分封裝在函數(shù)參數(shù)里,動態(tài)傳入Array.prototype.sort,使Array.prototype.sort方法成為了一個非常靈活的方法。
// 從小到大排序 console.log( // 輸出:[1, 3, 4] [1, 4, 3].sort(function(a, b){ return a - b; }) ); // 從大到小排序 console.log( // 輸出:[4, 3, 1] [1, 4, 3].sort(function(a, b){ return b - a; }) );函數(shù)作為返回值輸出1. 判斷數(shù)據(jù)的類型
判斷一個數(shù)據(jù)是否是數(shù)組,可以基于鴨子類型的理念來判斷,比如判斷這個數(shù)據(jù)有沒有l(wèi)ength熟悉,有沒有sort方法或者slice方法。但更好的方式是用Object.prototype.toString來計算。
Object.prototype.toString.call(obj)返回一個字符串,比如Object.prototype.toString.call([1,2,3])總是返回[Object Array],而Object.prototype.toString.call("str")總是返回[Object String]。所以我們可以編寫一系列isType函數(shù):
var isType = function(type){ return function(obj){ return Object.prototype.toString.call(obj) === "[object " + type + "]"; } }; var isString = isType("String"); var isArray = isType("Array"); var isNumber = isType("Number"); console.log(isArray([1,2,3])); // 輸出:true2. getSingle
有一種設(shè)計模式叫單例模式,下面是它的例子:
var getSingle = function(fn){ var ret; return function(){ return ret || (ret = fn.apply(this, arguments)); }; }; var getScript = getSingle(function(){ return document.createElement("script"); }); var script1 = getScript(); var script2 = getScript(); console.log(script1 === script2); // 輸出:true這個高階函數(shù)的例子,既把函數(shù)當作參數(shù)傳遞,又讓函數(shù)執(zhí)行后返回了另一個函數(shù)。
高階函數(shù)實現(xiàn)AOPAOP(面向切面編程)的主要作用是把一些跟核心業(yè)務邏輯模塊無關(guān)的功能抽離出來,這些跟業(yè)務邏輯無關(guān)的功能通常包括日志統(tǒng)計、安全控制、異常處理等。把這些功能抽離處理之后,再通過“動態(tài)織入”的方式摻入業(yè)務邏輯模塊中。這樣做的好處首先是可以保持業(yè)務邏輯模塊的純凈和高內(nèi)聚性,其次是可以很方便地復用日志統(tǒng)計等功能模塊。
在JavaScript中實現(xiàn)AOP,都是指把一個函數(shù)“動態(tài)織入”到另一個函數(shù)之中,具體的實現(xiàn)技術(shù)有很多,這里我們通過擴展Function.prototype來實現(xiàn)。
function.prototype.before = function(beforefn){ var __self = this; // 保存原函數(shù)的引用 return function(){ // 返回包含了原函數(shù)和新函數(shù)的“代理”函數(shù) beforefn.apply(this, arguments); // 執(zhí)行新函數(shù),修正this return __self.apply(this, arguments); // 執(zhí)行原函數(shù) } }; Function.prototype.after = function(afterfn){ var __self = this; return function(){ var ret = __self.apply(this, arguments); afterfn.apply(this, arguments); return ret; } }; var func = function(){ console.log(2); }; func = func.beforefn(function(){ console.log(1); }).after(function(){ console.log(3); }); func();這種使用AOP的方式來給函數(shù)添加職責,也是JavaScript語言中一種非常特別和巧妙的裝飾者模式實現(xiàn)。
PS:本節(jié)內(nèi)容為《JavaScript設(shè)計模式與開發(fā)實踐》第三章 筆記。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/86175.html
摘要:閉包的形成與變量的作用域以及變量的生存周期密切相關(guān)。現(xiàn)在我們把變量用閉包封閉起來,便能解決請求丟失的問題二高階函數(shù)高階函數(shù)是指至少滿足下列條件之一的函數(shù)。回調(diào)函數(shù)在異步請求的應用中,回調(diào)函數(shù)的使用非常頻繁。 一、閉包 對于 JavaScript 程序員來說,閉包(closure)是一個難懂又必須征服的概念。閉包的形成與變量的作用域以及變量的生存周期密切相關(guān)。下面我們先簡單了解這兩個知識...
摘要:自執(zhí)行函數(shù)閉包實現(xiàn)模塊化以樂之名程序員產(chǎn)品經(jīng)理對作用域,以及閉包知識還沒掌握的小伙伴,可回閱前端進擊的巨人三從作用域走進閉包。參考文檔利用閉包實現(xiàn)模塊化翻譯淺談中的高階函數(shù)系列更文請關(guān)注專欄前端進擊的巨人,不斷更新中。。。 系列更文前三篇文章,圍繞了一個重要的知識點:函數(shù)。函數(shù)調(diào)用棧、函數(shù)執(zhí)行上下文、函數(shù)作用域到閉包。可見不理解函數(shù)式編程,代碼都擼不好。 showImg(https:/...
摘要:理解的函數(shù)基礎(chǔ)要搞好深入淺出原型使用原型模型,雖然這經(jīng)常被當作缺點提及,但是只要善于運用,其實基于原型的繼承模型比傳統(tǒng)的類繼承還要強大。中文指南基本操作指南二繼續(xù)熟悉的幾對方法,包括,,。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。 怎樣使用 this 因為本人屬于偽前端,因此文中只看懂了 8 成左右,希望能夠給大家?guī)韼椭?...(據(jù)說是阿里的前端妹子寫的) this 的值到底...
摘要:設(shè)計模式是以面向?qū)ο缶幊虨榛A(chǔ)的,的面向?qū)ο缶幊毯蛡鹘y(tǒng)的的面向?qū)ο缶幊逃行┎顒e,這讓我一開始接觸的時候感到十分痛苦,但是這只能靠自己慢慢積累慢慢思考。想繼續(xù)了解設(shè)計模式必須要先搞懂面向?qū)ο缶幊蹋駝t只會讓你自己更痛苦。 JavaScript 中的構(gòu)造函數(shù) 學習總結(jié)。知識只有分享才有存在的意義。 是時候替換你的 for 循環(huán)大法了~ 《小分享》JavaScript中數(shù)組的那些迭代方法~ ...
摘要:可能因為先入為主,在編程之中,往往不由自主地以的邏輯編程思路設(shè)計模式進行開發(fā)。這是原型模式很重要的一條原則。關(guān)于閉包與內(nèi)存泄露的問題,請移步原型模式閉包與高階函數(shù)應該可以說是設(shè)計模式的基礎(chǔ)要領(lǐng)吧。在下一章,再分享一下的幾種常用設(shè)計模式。 前 在學習使用Javascript之前,我的程序猿生涯里面僅有接觸的編程語言是C#跟Java——忽略當年在大學補考了N次的C與VB。 從靜態(tài)編程語言,...
閱讀 1540·2023-04-26 02:08
閱讀 3133·2021-10-14 09:42
閱讀 7208·2021-09-22 15:34
閱讀 3241·2019-08-30 13:16
閱讀 2732·2019-08-26 13:49
閱讀 1348·2019-08-26 11:59
閱讀 1275·2019-08-26 10:31
閱讀 2174·2019-08-23 17:19