摘要:閉包的形成與變量的作用域以及變量的生存周期密切相關(guān)。現(xiàn)在我們把變量用閉包封閉起來,便能解決請求丟失的問題二高階函數(shù)高階函數(shù)是指至少滿足下列條件之一的函數(shù)。回調(diào)函數(shù)在異步請求的應用中,回調(diào)函數(shù)的使用非常頻繁。
一、閉包
對于 JavaScript 程序員來說,閉包(closure)是一個難懂又必須征服的概念。閉包的形成與
變量的作用域以及變量的生存周期密切相關(guān)。下面我們先簡單了解這兩個知識點。
變量的作用域,就是指變量的有效范圍。我們最常談到的是在函數(shù)中聲明的變量作用域。
當在函數(shù)中聲明一個變量的時候,如果該變量前面沒有帶上關(guān)鍵字 var,這個變量就會成為 全局變量,這當然是一種容易造成命名沖突的做法。
另外一種情況是用 var 關(guān)鍵字在函數(shù)中聲明變量,這時候的變量即是局部變量,只有在該函 數(shù)內(nèi)部才能訪問到這個變量,在函數(shù)外面是訪問不到的。代碼如下:
var func = function(){ var a = 1; alert ( a ); // 輸出: 1 }; func(); alert ( a ); // 輸出:Uncaught ReferenceError: a is not defined
在 JavaScript 中,函數(shù)可以用來創(chuàng)造函數(shù)作用域。此時的函數(shù)像一層半透明的玻璃,在函數(shù) 里面可以看到外面的變量,而在函數(shù)外面則無法看到函數(shù)里面的變量。這是因為當在函數(shù)中搜索 一個變量的時候,如果該函數(shù)內(nèi)并沒有聲明這個變量,那么此次搜索的過程會隨著代碼執(zhí)行環(huán)境 創(chuàng)建的作用域鏈往外層逐層搜索,一直搜索到全局對象為止。變量的搜索是從內(nèi)到外而非從外到 內(nèi)的。
下面這段包含了嵌套函數(shù)的代碼,也許能幫助我們加深對變量搜索過程的理解:
var a = 1; var func1 = function(){ var b = 2; var func2 = function(){ var c = 3; alert ( b ); // 輸出:2 alert ( a ); // 輸出:1 } func2(); alert ( c );// 輸出:Uncaught ReferenceError: c is not defined }; func1();1.2 變量的生存周期
除了變量的作用域之外,另外一個跟閉包有關(guān)的概念是變量的生存周期。
對于全局變量來說,全局變量的生存周期當然是永久的,除非我們主動銷毀這個全局變量。
而對于在函數(shù)內(nèi)用 var 關(guān)鍵字聲明的局部變量來說,當退出函數(shù)時,這些局部變量即失去了 它們的價值,它們都會隨著函數(shù)調(diào)用的結(jié)束而被銷毀:
var func = function(){ var a = 1; // 退出函數(shù)后局部變量 a 將被銷毀 alert ( a ); }; func();
現(xiàn)在來看看下面這段代碼:
var func = function(){ var a = 1; return function(){ a++; alert ( a ); } }; var f = func(); f(); // 輸出:2 f(); // 輸出:3 f(); // 輸出:4 f(); // 輸出:51.3閉包的作用 1.3.1 封裝變量
閉包可以幫助把一些不需要暴露在全局的變量封裝成“私有變量”。假設(shè)有一個計算乘積的
簡單函數(shù):
var mult = function(){ var a = 1; for ( var a = a } return a; }; i = 0, l = arguments.length; i < l; i++ ){ * arguments[i];
mult 函數(shù)接受一些 number 類型的參數(shù),并返回這些參數(shù)的乘積。現(xiàn)在我們覺得對于那些相同 的參數(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; }; alert ( mult( 1,2,3 ) ); // 輸出:6 alert ( mult( 1,2,3 ) ); // 輸出:6
我們看到 cache 這個變量僅僅在 mult 函數(shù)中被使用,與其讓 cache 變量跟 mult 函數(shù)一起平行 地暴露在全局作用域下,不如把它封閉在 mult 函數(shù)內(nèi)部,這樣可以減少頁面中的全局變量,以 4 避免這個變量在其他地方被不小心修改而引發(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 < l; i++ ){ a = a * arguments[i]; } return cache[ args ] = a; } })();
提煉函數(shù)是代碼重構(gòu)中的一種常見技巧。如果在一個大函數(shù)中有一些代碼塊能夠獨立出來, 我們常常把這些代碼塊封裝在獨立的小函數(shù)里面。獨立出來的小函數(shù)有助于代碼復用,如果這些 小函數(shù)有一個良好的命名,它們本身也起到了注釋的作用。如果這些小函數(shù)不需要在程序的其他 9 地方使用,最好是把它們用閉包封閉起來。代碼如下:
var cache = {}; var mult = (function(){ var cache = {}; var calculate = function(){ // 封閉 calculate 函數(shù) var a = 1; for ( var i = 0, l = arguments.length; i < l; i++ ){ a = a * arguments[i]; } return a; }; return function(){ var args = Array.prototype.join.call( arguments, "," ); if ( args in cache ){ return cache[ args ]; } return cache[ args ] = calculate.apply( null, arguments ); } })();1.3.2 延續(xù)局部變量的壽命
img 對象經(jīng)常用于進行數(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 請求,所以此次請求就會丟失掉。
現(xiàn)在我們把 img 變量用閉包封閉起來,便能解決請求丟失的問題:
var report = (function(){ var imgs = []; return function( src ){ var img = new Image(); imgs.push( img ); img.src = src; } })();二、高階函數(shù)
高階函數(shù)是指至少滿足下列條件之一的函數(shù)。
函數(shù)可以作為參數(shù)被傳遞;
函數(shù)可以作為返回值輸出。
JavaScript 語言中的函數(shù)顯然滿足高階函數(shù)的條件,在實際開發(fā)中,無論是將函數(shù)當作參數(shù)
傳遞,還是讓函數(shù)的執(zhí)行結(jié)果返回另外一個函數(shù),這兩種情形都有很多應用場景,下面就列舉一
些高階函數(shù)的應用場景。
把函數(shù)當作參數(shù)傳遞,這代表我們可以抽離出一部分容易變化的業(yè)務邏輯,把這部分業(yè)務邏
輯放在函數(shù)參數(shù)中,這樣一來可以分離業(yè)務代碼中變化與不變的部分。其中一個重要應用場景就
是常見的回調(diào)函數(shù)。
在 ajax 異步請求的應用中,回調(diào)函數(shù)的使用非常頻繁。當我們想在 ajax 請求返回之后做一
些事情,但又并不知道請求返回的確切時間時,最常見的方案就是把 callback 函數(shù)當作參數(shù)傳入
發(fā)起 ajax 請求的方法中,待請求完成之后執(zhí)行 callback 函數(shù):
var getUserInfo = function( userId, callback ){ $.ajax( "http://xxx.com/getUserInfo?" + userId, function( data ){ if ( typeof callback === "function" ){ callback( data ); } }); } getUserInfo( 13157, function( data ){ alert ( data.userName ); });
回調(diào)函數(shù)的應用不僅只在異步請求中,當一個函數(shù)不適合執(zhí)行一些請求時,我們也可以把這些請求封裝成一個函數(shù),并把它作為參數(shù)傳遞給另外一個函數(shù),“委托”給另外一個函數(shù)來執(zhí)行。
2. Array.prototype.sortArray.prototype.sort 接受一個函數(shù)當作參數(shù),這個函數(shù)里面封裝了數(shù)組元素的排序規(guī)則。從Array.prototype.sort 的使用可以看到,我們的目的是對數(shù)組進行排序,這是不變的部分;而使用 什 么 規(guī) 則 去 排 序 , 則 是 可 變 的 部 分 。 把 可 變 的 部 分 封 裝 在 函 數(shù) 參 數(shù) 里 , 動 態(tài) 傳 入Array.prototype.sort,使 Array.prototype.sort 方法成為了一個非常靈活的方法,代碼如下:
//從小到大排列 [ 1, 4, 3 ].sort( function( a, b ){ return a - b; }); // 輸出: [ 1, 3, 4 ] //從大到小排列 [ 1, 4, 3 ].sort( function( a, b ){ return b - a; }); // 輸出: [ 4, 3, 1 ]2.2 函數(shù)作為返回值輸出
相比把函數(shù)當作參數(shù)傳遞,函數(shù)當作返回值輸出的應用場景也許更多,也更能體現(xiàn)函數(shù)式編程的巧妙。讓函數(shù)繼續(xù)返回一個可執(zhí)行的函數(shù),意味著運算過程是可延續(xù)的。
1. 判斷數(shù)據(jù)的類型我們來看看這個例子,判斷一個數(shù)據(jù)是否是數(shù)組,在以往的實現(xiàn)中,可以基于鴨子類型的概念來判斷,比如判斷這個數(shù)據(jù)有沒有 length 屬性,有沒有 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 isString = function( obj ){ return Object.prototype.toString.call( obj ) === "[object String]"; }; var isArray = function( obj ){ return Object.prototype.toString.call( obj ) === "[object Array]"; }; var isNumber = function( obj ){ return Object.prototype.toString.call( obj ) === "[object Number]"; };
我們發(fā)現(xiàn),這些函數(shù)的大部分實現(xiàn)都是相同的,不同的只是 Object.prototype.toString.call( obj )返回的字符串。為了避免多余的代碼,我們嘗試把這些字符串作為參數(shù)提前值入 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 ] ) ); // 輸出: true
我們還可以用循環(huán)語句,來批量注冊這些 isType 函數(shù):
var Type = {}; for ( var i = 0, type; type = [ "String", "Array", "Number" ][ i++ ]; ){ (function( type ){ Type[ "is" + type ] = function( obj ){ return Object.prototype.toString.call( obj ) === "[object "+ type +"]"; } })( type ) }; Type.isArray( [] ); // 輸出: true Type.isString( "str" ); // 輸出: true2. getSingle
下面是一個單例模式的例子,在第三部分設(shè)計模式的學習中,我們將進行更深入的講解,這
里暫且只了解其代碼實現(xiàn):
var getSingle = function ( fn ) { var ret; return function () { return ret || ( ret = fn.apply( this, arguments ) ); }; };
這個高階函數(shù)的例子,既把函數(shù)當作參數(shù)傳遞,又讓函數(shù)執(zhí)行后返回了另外一個函數(shù)。我們可以看看 getSingle 函數(shù)的效果:
var getScript = getSingle(function(){ `return document.createElement( "script" ); }); var script1 = getScript(); var script2 = getScript(); alert ( script1 === script2 ); // 輸出: true
注:內(nèi)容摘取《Javascript設(shè)計模式與開發(fā)實踐》
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/100557.html
摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。異步編程入門的全稱是前端經(jīng)典面試題從輸入到頁面加載發(fā)生了什么這是一篇開發(fā)的科普類文章,涉及到優(yōu)化等多個方面。 TypeScript 入門教程 從 JavaScript 程序員的角度總結(jié)思考,循序漸進的理解 TypeScript。 網(wǎng)絡(luò)基礎(chǔ)知識之 HTTP 協(xié)議 詳細介紹 HTT...
摘要:可能因為先入為主,在編程之中,往往不由自主地以的邏輯編程思路設(shè)計模式進行開發(fā)。這是原型模式很重要的一條原則。關(guān)于閉包與內(nèi)存泄露的問題,請移步原型模式閉包與高階函數(shù)應該可以說是設(shè)計模式的基礎(chǔ)要領(lǐng)吧。在下一章,再分享一下的幾種常用設(shè)計模式。 前 在學習使用Javascript之前,我的程序猿生涯里面僅有接觸的編程語言是C#跟Java——忽略當年在大學補考了N次的C與VB。 從靜態(tài)編程語言,...
摘要:自我學習目前有成千上萬的年輕人在學習和開發(fā),希望獲得一份工作。知道的綁定規(guī)則。知道和原型屬性是什么以及它們的作用。高階函數(shù)了解函數(shù)是中的一級對象,這意味著什么知道從另一個函數(shù)返回函數(shù)是完全合法的。了解閉包和高階函數(shù)允許我們使用的情況。 翻譯原文出處:10 JavaScript concepts you need to know for interviews 之前不是鬧得沸沸揚揚的大漠窮...
摘要:理解的函數(shù)基礎(chǔ)要搞好深入淺出原型使用原型模型,雖然這經(jīng)常被當作缺點提及,但是只要善于運用,其實基于原型的繼承模型比傳統(tǒng)的類繼承還要強大。中文指南基本操作指南二繼續(xù)熟悉的幾對方法,包括,,。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。 怎樣使用 this 因為本人屬于偽前端,因此文中只看懂了 8 成左右,希望能夠給大家?guī)韼椭?...(據(jù)說是阿里的前端妹子寫的) this 的值到底...
閱讀 2889·2021-11-17 09:33
閱讀 3662·2021-11-16 11:42
閱讀 3488·2021-10-26 09:50
閱讀 1318·2021-09-22 15:49
閱讀 3046·2021-08-10 09:44
閱讀 3670·2019-08-29 18:36
閱讀 3925·2019-08-29 16:43
閱讀 2208·2019-08-29 14:10