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