摘要:高階函數不是的所特有的,其他編程語言也有。高階函數面向切面編程面向切面編程這種思想在開發中比較常見,主要就是將一些與核心業務無關的功能抽離出來,比如異常處理,日志統計等。
javascript的函數式語言特性
我們知道JavaScript使一門面向對象的編程語言,但這門語言同時擁有很多函數式語言的特性。
JavaScript的設計者在設計最初就參考了LISP方言之一的Scheme,引入了Lambda表達式、閉包、高階函數等內容,正是因為這些特性讓JavaScript靈活多變。
Lambda(匿名函數)表達式lambda在JavaScript中通常被引用做匿名函數使用,被用作一個值傳遞給其他函數,或者把一個行為當作值來傳遞。
在ES6之前,我們使用這樣的函數表達式,我們可以將一個匿名函數指定給一個變量。
var add = function(a, b) { return a + b }
而在ES6中,我們使用箭頭函數,它的語法更靈活,它有一些新的特性和陷阱。
// 我們可以寫成下面的形式 var add = (a, b) => a + b; // 或者 var add = (a, b) => { return a + b };
箭頭函數的優勢就是它沒有自己的this,我們往往會遇到匿名函數的作用域特殊處理的情況,如果使用箭頭函數就可以避免這樣的情況。
var id = "global"; var obj = {}; obj.id = "inner"; obj.delayWork = function() { setTimeout(function() { console.log(this.id); }) } obj.delayWork(); // global
我們的本意是想讓對象調用方法輸出它的屬性id,結果輸出的確是全局屬性window的id,如下面使用箭頭函數即可輸出正確的結果;
var id = "global"; var obj = {}; obj.id = "inner"; obj.delayWork = function() { setTimeout(() => { console.log(this.id); }) } obj.delayWork(); // inner
在這里是箭頭函數的優勢,但是在沒有出現箭頭函數前我們用的方法是:
var id = "global"; var obj = {}; obj.id = "inner"; obj.delayWork = function() { var that = this; setTimeout(function () { console.log(that.id); }) } obj.delayWork(); // inner
這種方法有些人稱為that方法,但是我們看英文的話都是用 jumping this , 很明顯這里的意思就是跳出this的對象指代,我們可以在我們能確定this指代的對象的地方用that保存this,后面用到this都用that來取代。
箭頭函數的短板:
在函數內不能使用call,apply來改變函數的內this
函數沒有arguments
關于this,apply/call理解不太深的可以參考這表篇文章this,call和apply(這三個東西,如何牢牢記住)
兩種形式的lambda的使用各有優略勢,上面的示例就是兩種lambda的搭配使用。
閉包閉包了解了不一定就懂,懂了也并不一定會很好的使用,對于JavaScript程序員來說,它就是一座大山,前端開發人員需要邁過去的大山。
理解閉包,需要理解閉包的形成與變量的作用域以及變量的生存周期。
變量的作用域變量的作用域就是指變量的有效范圍。
在函數中生命的變量的時候,變量前帶關鍵字var,這個變量就會成為局部變量,只有在該函數內部才能訪問這個變量;如果沒有var關鍵字,就是全局變量,我們要注意這樣的定義變量會造成命名沖突。
補充一點,函數可以用來創建函數作用域,有人不認為應該把函數當做作用域理解,認為函數就是一個代碼塊。我更傾向于后者,這里只不過是給大家補充點小知識。在函數里我們可以使用函數外的變量,但是在函數外卻不能使用函數內的變量。對JavaScript的原型有深刻理解的同學都會明白,它會順著原型鏈(有人會稱之為作用域鏈)逐層向外搜索,一直到全局對象位置,所以是不能通過原型鏈向內搜索的。
變量的生存周期對于全局變量來說,它的生存周期是永久的,除非手動的銷毀這個全局變量。
而對于局部變量來說,當函數調用結束的時候就會被銷毀。
我們知道在開發的過程中我們不想定義更多的全局變量污染全局環境,我們又想使變量擁有永久的生存周期,同時我們又要變量的私有化。在這樣矛盾的開發需求下,JavaScript閉包應運而生。
var cAlert = function() { var a = 1; return function(){ a++; alert(a) } } var f = cAlert(); f();
這是一個常見的閉包例子,但實現上面的效果我們也可以這樣做:
var myNameSpace = {}; // 許可的全局命名空間 myNameSpace.a = 1; myNameSpace.alert = function() { this.a++; alert(this.a) }; myNameSpace.alert();
對于 a 我們可以有兩種方法讓它像全局變量一樣擁有永久的生命周期,一種是使用閉包,一種是使用對象的屬性,因為它們分別在全局的f,myNameSpace中被引用,所以他們的生命周期得以延長,因為眾所周知,全局的生命周期是永久的;它們都是在全局變量下被定義,因此,保持了私有性;避免了全局污染。
雖然第二種方法也可以實現這種好處,但是你依然離不開閉包,閉包是從可以進行局部處理,而第二種方法它是從全局入手的。如:我們操作一個有序列表,單擊每一項彈出他們的索引。代碼如下:
關于閉包的其它知識點你可以看老生常談之閉包 這篇文章,這篇文章JavaScript語言的函數特性。
高階函數我記得一次面試時就被問到高階函數,題目的大致意思什么是高階函數,高階函數有什么特性,談談你對高階函數的理解。說實話,當時我根本就不知到什么是高階函數,只知道JavaScript函數的特殊用法,就說了在閉包中函數可以當做返回值來用,函數可以當作另一個函數的參數被引用等。雖然知道這些,但是不知道為什么這樣用,知道是因為接觸過閉包,用過一些JavaScript的對象方法如:Array.prototype.reduce(callback[, initial]) 等這樣的JavaScript內置方法,但‘知其然,不知其所以然’。然后就是談自己的感受,因為只會使用,所以說不出個所以然來。
高階函數不是JavaScript的所特有的,其他編程語言也有。JavaScript中高階函數和其他語言一樣要滿足如下兩個條件:
函數可以作為參數被傳遞
函數可以作為返回值被輸出
這兩點的使用在JavaScript中很常見,有的同學可能就不以為然,不就是這嗎,我在平常開發中經常見到,但是問題往往不敢發散,特別是面試的時候,知道歸知道,用過歸用過,但能不能說出個一二三來,就是另一回事,在面試的時候,面試官往往不是問你概念的,你不知道有時也沒事兒,但是你要理解如何用,以及用它能干些什么。
下面就分別介紹一下它們的應用場景。
函數被作為參數傳遞把函數作為參數傳遞,是因為在開發中我們有很多易變的業務邏輯,如果對于這部分易變的業務邏輯我們可以把它當作參數處理,這樣就大大的方便了我們的開發。就如同我們在日常開發中遵循的將業務中變化的部分和不變的部分分開一樣(業務分離)。
向頁面body內添加一個div,然后設置div元素隱藏。
function appendDiv(){ var oDiv = document.createElement("div"); oDiv.className = "myDiv"; oDiv.style.display = "none"; document.body.appendChild(oDiv); } appendDiv();
在日常的開發中我們經常見到有人這樣實現,雖然達到了目的,但是為了實現代碼片段的可復用性,我們應盡量避免硬編碼的情況出現。
有時在面試中往往會遇到面試官讓我們寫一些看起來很簡單的實現,就像上面的情況,這種答案雖然正確,但不是面試官所想要的答案,面試官會用其他的問題來驗證你是否是他需要的開發人員。
為了達到代碼的可復用和可維護,我們可以這樣實現;
function appendDiv(callback){ var oDiv = document.createElement("div"); oDiv.className = "myDiv"; if(callback && typeof callback === "function") { callback.call(null, oDiv); } document.body.appendChild(oDiv); } appendDiv(function(node) { node.style.display = "none" });
上面的代碼是不是很熟悉,相信這樣的代碼對于經常學習研究源碼的你來說并不陌生。
就像我們經常使用的數組內置函數 Array.prototype.filter() ,Array.prototype.reduce() ,Array.prototype.map() 等一樣把變化的的部分封裝在回調函數中一樣,在開發中我們也要經常使用這樣的形式,告別硬編碼。
var arr = [1, 2, 3, 4, 5]; var newArray = arr => arr.filter((item) => item%2 === 0); newArray(arr); // [2, 4]
函數作為回調函數使用場景還有很多,比如,在開發中使用的Ajax請求等。
函數作為返回值輸出函數作為返回值在我們的開發中也比較常見,例如我們說熟悉的閉包,這里就不介紹閉包了,我們用一個對象數組排序的例子來說明一下:
var personList = [ {name: "許家印", worth: "2813.5", company: "恒大集團"}, {name: "馬云", worth: "2555.3", company: "阿里巴巴"}, {name: "王健林", worth: "1668.2", company: "大連萬達集團"}, {name: "馬化騰", worth: "2581.8", company: "騰訊"}, {name: "李彥宏", worth: "1132", company: "百度"} ]; // 排序規則 function compareSort(item, order) { // 排序規則的具體實現 return function(a, b) { if(order && oder === "asc") { return a[item] - b[item] } else { return b[item] - a[item] } } } // 用compareSort的參數來實現自定義排序 personList.sort(compareSort("worth", "desc")); /* [{name: "許家印", worth: "2813.5", company: "恒大集團"}, {name: "馬化騰", worth: "2581.8", company: "騰訊"}, {name: "馬云", worth: "2555.3", company: "阿里巴巴"}, {name: "王健林", worth: "1668.2", company: "大連萬達集團"}, {name: "李彥宏", worth: "1132", company: "百度"}] */
我們在開發中經常會遇到這樣的數據排序——自定義排序。
高階函數AOP (面向切面編程)面向切面編程這種思想在開發中比較常見,主要就是將一些與核心業務無關的功能抽離出來,比如異常處理,日志統計等。在開發中根據需要我們再通過 動態織入 的方式將這些分離出來的功能模塊摻入業務邏輯塊中。
這樣做不僅可以保持業務邏輯模塊的純凈和高內聚,還可以方便我們復用分離的模塊。
我發現以前學習jQuery的源碼只是學習了源碼的功能實現。通過對比學習,我慢慢開始嘗試理解jQuery的業務實現和架構組成。
在JavaScript這個基于 prototype 的動態語言實現面向切面編程很簡單。
Function.prototype.success = function(fn) { var that = this; return function() { var ret = that.apply(this, arguments) fn.apply(this, arguments); return ret; } }; Function.prototype.fail = function(fn) { var that = this; return function() { var ret = that.apply(this, arguments) fn.apply(this, arguments); return ret; } }; function ajax () { console.log("get it.") } var get = ajax.success(function() { console.log("success"); }).fail(function() { console.log("fail"); }); get();
這里模擬了一個jQuery的Ajax的形式實現,有一個核心模塊 ajax ,我們我將 success,fail 模塊分離出來,這樣就可以實現模塊的 動態織入 。
高階函數的應用 函數節流在開發中有些函數不是用戶直接觸發控制的,在這樣的情況下就可能出現函數被頻繁調用的情況,這樣往往會引起性能問題。
函數頻繁調用的場景歸納:
window.onresize事件
當調整瀏覽器窗口大小時,這個事件會被頻繁出發,而在這個時間函數中的dom操縱也會很頻繁,這樣就會造成瀏覽器卡頓現象。
mousemove事件
當被綁定該事件的dom對象被拖動時,該事件會被頻繁觸發。
所以,函數節流的原理就是在不影響使用效果的情況下降低函數的觸發頻率。
var throttle = function ( fn, interval ) { var __self = fn, // 保存需要被延遲執行的函數引用 timer, // 定時器 firstTime = true; // 是否是第一次調用 return function () { var args = arguments, __me = this; // 如果是第一次調用,不需延遲執行 if ( firstTime ) { __self.apply(__me, args); return firstTime = false; } // 如果定時器還在,說明前一次延遲執行還沒有完成 if ( timer ) { return false; } // 延遲一段時間執行 timer = setTimeout(function () { clearTimeout(timer); timer = null; __self.apply(__me, args); }, interval || 500 ); }; }; window.onresize = throttle(function(){ console.log(1); }, 500 );分時函數
在開發中有時我們也會遇到,是用戶主動觸發的操作,倒是瀏覽器的卡頓或假死。例如,我用戶批量操作向頁面添加dom元素時,為了防止出現瀏覽器卡頓或假死的情況,我們可以每隔幾秒向頁面添加固定數量的元素節點。
// 創建一個數組,用來存儲添加到dom的數據 var dataList = []; // 模擬生成500個數據 for (var i = 1; i <= 500; i++) { dataList.push(i); } // 渲染數據 var renderData = timeShareRender(dataList, function(data) { var oDiv = document.createElement("div"); oDiv.innerHTML = data; document.body.appendChild(oDiv); }, 6); // 分時間段將數據渲染到頁面 function timeShareRender(data, fn, num) { var cur, timer; var renderData = function() { for(var i = 0; i < Math.min(count, data.length); i++) { cur = data.shift(); fn(cur) } }; return function() { timer = setInterval(function(){ if(data.length === 0) { return clearInterval(timer) } renderData() }, 200); } } // 將數據渲染到頁面 renderData();
demo演示
惰性加載函數在web開發中,因為瀏覽器的差異性,我們經常會用到嗅探。那就列舉一個我們比較常見的使用惰性加載函數的事件綁定函數 removeEvent 的實現:
一般我們都這樣寫:
var removeEvent = function(elem, type, handle) { if(elem.removeEventListener) { return elem.removeEventLisener(type, handle, false) } if(elem.detachEvent) { return elem.detachEvent( "on" + type, handle ) } }
但是我們卻發現jQuery 中是這樣實現的
removeEvent = document.removeEventListener ? function( elem, type, handle ) { if ( elem.removeEventListener ) { elem.removeEventListener( type, handle, false ); } } : function( elem, type, handle ) { if ( elem.detachEvent ) { elem.detachEvent( "on" + type, handle ); } }
jQuery的寫法避免了每次使用 removeEvent 都要進行的多余的條件判斷,只要進行第一次的嗅探判斷,第二次就可以直接使用該事件,但是前一種則是需要每次都進行嗅探判斷,所以第二種的寫法在開銷上要比第一種低的多。
github.com/lvzhenbang/article
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/92437.html
摘要:前言在學習前端的時候,我總是能聽到很多高級詞匯,比如今天會聊到的函數式編程高階函數。接下來我們看看,高階函數有可能會遇到的問題,又如何去解決。 前言 在學習前端的時候,我總是能聽到很多高級詞匯,比如今天會聊到的 函數式編程(Functional Programming) & 高階函數 (Higher-order function) 。但是當你真正的理解什么是 函數式編程 & 高階函數 ...
摘要:前端日報精選漫談函數式編程一十年蹤跡的博客前端每周清單的優勢與劣勢有望超越在嵌入式及物聯網的應用現狀進階系列高階組件詳解一前端之路譯如何充分利用控制臺掘金程序猿升級攻略眾成翻譯中文譯如何充分利用控制臺掘金前端從強制開啟壓縮探 2017-06-27 前端日報 精選 漫談 JS 函數式編程(一) - 十年蹤跡的博客前端每周清單: Vue的優勢與劣勢;Node.js有望超越Java;JS在嵌...
摘要:前端日報精選譯中一些超級好用的內置方法漫談組件庫開發一多層嵌套彈層組件高階組件淺析的工廠函數打包優化之速度篇中文教程用純實現跳跳球動畫眾成翻譯個幫助你學習的快速且久經考驗的技巧眾成翻譯自定義屬性使用進行動態更改眾成翻譯真假值知多 2017-08-26 前端日報 精選 【譯】ES6中一些超!級!好!用!的內置方法漫談 React 組件庫開發(一):多層嵌套彈層組件React 高階組件淺析...
閱讀 1483·2023-04-25 15:40
閱讀 2834·2021-08-11 11:15
閱讀 2273·2019-08-26 13:48
閱讀 2844·2019-08-26 12:18
閱讀 2448·2019-08-23 18:23
閱讀 2905·2019-08-23 17:01
閱讀 2978·2019-08-23 16:29
閱讀 1101·2019-08-23 15:15