摘要:我們需要知道的是,對于而言,匿名函數(shù)是一個很重要且具有邏輯性的特性。通常,匿名函數(shù)的使用情況是創(chuàng)建一個供以后使用的函數(shù)。截圖自忍者秘籍通過完善之前對匿名函數(shù)的粗略定義,我們可以修復(fù)解決這個問題。
匿名函數(shù)從名字即可看書,此篇博客總結(jié)與《JavaScript忍者秘籍》。對于JavaScript來說,函數(shù)為第一類型對象。所以這里,我們主要是介紹JavaScript中函數(shù)的運用。
系列博客地址:https://github.com/Nealyang/YOU-SHOULD-KNOW-JS
對于什么是匿名函數(shù),這里就不做過多介紹了。我們需要知道的是,對于JavaScript而言,匿名函數(shù)是一個很重要且具有邏輯性的特性。通常,匿名函數(shù)的使用情況是:創(chuàng)建一個供以后使用的函數(shù)。
簡單的舉個例子如下:
window.onload = function() { alert("hello"); } var templateObj = { shout:function() { alert("作為方法的匿名函數(shù)") } } templateObj.shout(); setTimeout(function() { alert("這也是一個匿名函數(shù)"); },1000)
上面的一個代碼片段我就不做過多無用解釋了,比較常規(guī)。
遞歸遞歸,說白了,就是自己調(diào)用自己,或者調(diào)用另外一個函數(shù),但是這個函數(shù)的調(diào)用樹的某一個地方又調(diào)用了自己。所以遞歸,就產(chǎn)生了。
普通命名函數(shù)的遞歸拿普通命名函數(shù)的遞歸最好的舉例就是用最簡單的遞歸需求:檢測回文。
回文的定義如下:一個短語,不管從哪一個方向讀,都是一樣的。檢測的工作當(dāng)然方法多樣,我們可以創(chuàng)建一個函數(shù),用待檢測的回文字符逆序生成出一個字符,然后檢測二者是否相同,如果相同,則為回文字符。
但是這種方法并不是很有逼格,確切的說,代價比較大,因為我們需要分配并創(chuàng)建新的字符。
所以,我們可以整理出如下簡潔的辦法:
單個和零個字符都是回文
如果字符串的第一個字符和最后一個字符相同,并且除了兩個字符以外,別的字符也滿足該要求,那么我們就可以檢測出來了這個是回文了
function isPalindrome(txt) { if(txt.length<=1){ return true; } if(txt.charAt(0)!= txt.charAt(txt.length-1)) return false; return isPalindrome(txt.substr(1,txt.length-2)); }
上面的代碼我們并沒有做txt的一些類型檢測,undefined、null等。
方法中的遞歸所謂的方法,自然離不開對象,直接看例子:
var ninja = { chirp:function(n) { return n>1?ninja.chirp(n-1)+"-chirp":"chirp"; } } console.log(ninja.chirp(3))//chirp-chirp-chirp
在上述代碼中,我們通過對象ninja.chirp方法的遞歸調(diào)用了自己。但是,因為我們在函數(shù)上s會用了非直接引用,也就是ninja對象的chirp屬性,所以才能夠?qū)崿F(xiàn)遞歸,這也就引出來一個問題:引用丟失
引用丟失的問題上面的示例代碼,依賴于一個進行遞歸調(diào)用的對象屬性引用。與函數(shù)的實際名稱不同,因為這種引用可能是暫時的。
var ninja = { chirp:function(n) { return n>1?ninja.chirp(n-1)+"-chirp":"chirp"; } } var samurai = {chirp:ninja.chirp}; ninja = {}; try{ console.log(samurai.chirp(3) === "chirp-chirp-chirp") }catch (err){ if(err) alert(false); }
如上,我們把ninja屬性上的方法賦值給了samurai,然后置空ninja,然后你懂得~這就是引用丟失的問題。
截圖自《JavaScript忍者秘籍》
通過完善之前對匿名函數(shù)的粗略定義,我們可以修復(fù)解決這個問題。在匿名函數(shù)中,我們不在使用顯示的ninja引用。這里我們使用this(關(guān)于this的使用詳解,請關(guān)注我的個人微信公眾號:前端的全棧之路)。
var ninja = { chirp:function(n) { return n>1?this.chirp(n-1)+"-chirp":"chirp"; } }
當(dāng)函數(shù)作為方法被調(diào)用的時候,函數(shù)的上下文指的是該方法的對象。
使用this調(diào)用,可以讓我們的匿名函數(shù)更加的強大且靈活。但是。。。
內(nèi)聯(lián)命名函數(shù)上面我們解決了作為函數(shù)方法作為遞歸時候的一個完美操作。但實際上,不管是否進行方法遞歸,巧妙使用this都是我們應(yīng)該所掌握的(關(guān)注微信公眾號,早晚都給你說到)。
話說回來,其實這樣寫也還是有問題的,問題在于給對象定義方法的時候,方法名稱是寫死的,如果屬性名稱不一樣,豈不是一樣會丟失引用?
所以,這里我們采用另一種解決方案,給匿名函數(shù)起個名字吧!對的,肯定又人會說,我擦!那還是匿名函數(shù)么?嗯。。。好吧,那就不叫匿名函數(shù)了吧,叫內(nèi)聯(lián)函數(shù)~
var ninja = { chirp:function signal(n) { return n>1?signal(n-1)+"-chirp":"chirp"; } } var samurai = {chirps:ninja.chirp}; ninja = {}; try{ console.log(samurai.chirps(3) === "chirp-chirp-chirp") }catch (err){ if(err) alert(false); }
所以如上的解決辦法,就完美解決了我們之前說到所有問題。內(nèi)聯(lián)函數(shù)還有一個很重要的一點,就是盡管可以給內(nèi)聯(lián)函數(shù)進行命名,但是這些名稱只能在自身函數(shù)內(nèi)部才可見。
將函數(shù)視為對象JavaScript中的函數(shù)和其他語言中的函數(shù)有所不同,JavaScript賦予了函數(shù)很多的特性,其中最重要的特性之一就是函數(shù)作為第一類型對象。是的,對象!
所以,我們可以給函數(shù)添加屬性,甚至可以添加方法。
函數(shù)存儲有時候,我們可能需要存儲一組相關(guān)但又獨立的函數(shù),事件回調(diào)管理是最為明顯的例子。向這個集合添加函數(shù)時候,我們得知道哪些函數(shù)在集合中存在,否則不添加。
var store = { nextId:1, cache:{}, add:function(fn) { if(!fn.id){ fn.id = store.nextId++; return !!(store.cache[fn.id] = fn); } } } function ninja() {} console.log(store.add(ninja)); console.log(store.add(ninja));
上述代碼比較簡單常規(guī),也就不做過多解釋。
自記憶函數(shù)緩存記憶是構(gòu)造函數(shù)的過程,這種函數(shù)能夠記住先前計算的結(jié)果。通過避免重復(fù)的計算,極大地提高性能。
緩存記憶昂貴的計算結(jié)果作為一個簡單的例子,這里我來判斷一個數(shù)字是否為素數(shù)。
function isPrime(value) { if(!isPrime.answers) isPrime.answers = {}; if(isPrime.answers[value]!=null){ return isPrime.answers[value] } var prime = value != 1;//1 不是素數(shù) for(var i = 2;i如上代碼也都是常規(guī)操作,不做過多解釋。我們可以通過下面的console.log判斷出緩存是否成功。
緩存記憶有兩個主要的優(yōu)點:
在函數(shù)調(diào)用獲取之前計算結(jié)果的時候,最終用戶享有性能優(yōu)勢
發(fā)生在幕后,完全無縫,最終用戶和開發(fā)者都無需任何特殊的操作或者為此做任何初始化工作。
當(dāng)然,總歸會有缺點的
為了提高性能,任何類型的緩存肯定會犧牲內(nèi)存
純粹主義者可能認(rèn)為緩存這個問題不應(yīng)該與業(yè)務(wù)邏輯放到一起。一個函數(shù)或者方法只應(yīng)該做一件事。
很難測試和測量一個算法的性能。(比如我們這個“簡單”的例子)
緩存DOM記憶通過元素標(biāo)簽名來獲取DOM元素是一個非常常見的操作。但是性能可能不是特別好。所以從上面的緩存記憶我們可以進行如下的騷操作:
function getElements(name) { if(!getElements.cache) getElements.cache = {}; return getElements.cache[name] = getElements.cache[name]||document.getElementsByTagName(name); }上面的代碼很簡單,但是有么有眼前一亮的感覺呢??我有!而且我們還發(fā)現(xiàn),這個簡單的緩存的代碼產(chǎn)生了5倍以上的性能提升。
我們可以將狀態(tài)和緩存信息存儲在一個封裝的獨立位置上,不僅在代碼組織上有好處,而且外部存儲或緩存對象無需污染作用域,就可以獲取性能的提升。
別激動,下面還有更多的奇淫技巧~
偽造數(shù)組方法有時候我們想創(chuàng)建一個包含一組數(shù)據(jù)的對象。如果只是集合,則只需要創(chuàng)建一個數(shù)組即可。但是在某些情況下,除了集合本身,可能會有更多的狀體需要保存。
一種選擇是,每次創(chuàng)建對象新版本的時候都創(chuàng)建一個新數(shù)組,然后將元數(shù)據(jù)作為屬性或者方法添加到這個新數(shù)組上。但是這個操作太常規(guī)了。
欣賞如下騷操作:
通常,Array.prototype.push()是通過其函數(shù)上下文操作其自身數(shù)組的。這里我們通過call方法來講我們自己的對象扮演了一次他的上下文。push的方法會增加length的值(會認(rèn)為他就是數(shù)組的length屬性),然后給對象添加一個數(shù)字屬性,并將其引用到傳入的元素上。
關(guān)于函數(shù)的執(zhí)行上下文,以及prototype的一些說明,將在后續(xù)文章寫到。
可變函數(shù)的參數(shù)列表JavaScript靈活且強大的特性之一是函數(shù)可以接受任意數(shù)量的參數(shù)。雖然JavaScript沒有函數(shù)的重載,但是參數(shù)列表的靈活性是獲取其他語言類似重載功能的關(guān)鍵所在
使用apply()支持可變參數(shù)需求:查找數(shù)組中的最大值、最小值
一開始,我認(rèn)為Math中提供的min(),max()可以滿足,但是貌似他并不能夠找到數(shù)組中的最大值最小值,難道要我這樣:Math.min(arr[0],arr[1],arr[3]...)??
來吧,我們繼續(xù)我們的奇淫技巧。
function smallest(arr) { return Math.min.apply(Math,arr); } function largest(arr) { return Math.max.apply(Math,arr); } console.log(smallest([0,1,2,3,4])); console.log(largest([0,1,2,3,4]));不做過多解釋,操作常規(guī),是不是又是一個眼前一亮呢?
函數(shù)重載之前我們有介紹過函數(shù)的隱士傳遞,arguments,也正是因為這個arguments的存在,才讓函數(shù)有能力處理不同數(shù)量的參數(shù)。即使我們只定義固定數(shù)量的形參,通過arguments參數(shù)我們還是可以訪問到實際傳給函數(shù)的所有的參數(shù)。
檢測并遍歷參數(shù)方法的重載通常是通過在同名的方法里聲明不同的實例來達到目的。但是在javascript中并非如此,在javaScript中,我們重載函數(shù)的時候只有一個實現(xiàn)。只不過這個實現(xiàn)內(nèi)部是通過函數(shù)實際傳入的參數(shù)的特性和個數(shù)來達到相應(yīng)目的的。
function merge(root){ for(var i = 1;i通過如上代碼,我們將傳遞給函數(shù)的對象都合并到一個對象中。在javascript中,沒有強制函數(shù)聲明多少個參數(shù)就得穿入多少個參數(shù)。函數(shù)是否可以成功處理這些參數(shù),完全取決于函數(shù)本身的定義。
注意,我們要做的事情是想讓第二個或者第n個參數(shù)上的屬性合并到第一個對象中,所以這個遍歷是從1開始的。
利用參數(shù)個數(shù)進行函數(shù)的重載基于函數(shù)的參數(shù),有很多種辦法進行函數(shù)的重載。一種通用的方法是,根據(jù)傳入?yún)?shù)的類型執(zhí)行不同的操作。另一種辦法是,可以通過某些特定參數(shù)是否存在來進行判斷。還有一種是通過傳入?yún)?shù)個數(shù)來進行判斷。
假如對象上有一個方法,根據(jù)傳入?yún)?shù)的個數(shù)來執(zhí)行不同的操作,冗長且呆呆的函數(shù)應(yīng)該張這樣:
var ninja = { whatever:function(){ switch(arguments.length){ case:0: //do something break; case:1: //do something break; case:2: //do something break; case:3: //do something break; } } }這種方式,看起來非常的呆呆的。所以我們換一種方式來說下。
如果按照如下思路,添加重載的方法會怎樣呢。
var ninja = {}; addMethod(ninja,"whatever",function(){/*do something*/}); addMethod(ninja,"whatever",function(a){/*do something*/}); addMethod(ninja,"whatever",function(a,b){/*do something*/});這里我們使用同樣的名稱(whatever)將方法添加到該對象上,只不過每個重載的函數(shù)是多帶帶的。注意每一個重載的函數(shù)參數(shù)是不同的。通過這種方式,我們真正為每一個重載都創(chuàng)建了一個獨立的匿名函數(shù)。漂亮且簡潔。
下面就讓我操刀來實現(xiàn)這個addMethod函數(shù)吧
function addMethod(object,name,fn){ var old = object[name]; object[name] = function(){ if(fn.length === arguments.length){ return fn.apply(this,arguments); }else if(typeof old == "function"){ return old.apply(this,arguments); } } }這個操作我們這里解釋一下,第一步,我們保存原有的函數(shù),因為調(diào)用的時候可能不匹配傳入的參數(shù)個數(shù)。第二部創(chuàng)建一個新的匿名函數(shù),如果該匿名函數(shù)的形參個數(shù)和實際個數(shù)匹配,就調(diào)用這個函數(shù),否則調(diào)用原來的函數(shù)。
這里的fn.length是返回函數(shù)定義時候定義的形參個數(shù)。
下面解釋下這個函數(shù)的執(zhí)行吧。adMethod第一次調(diào)用將創(chuàng)建一個新的匿名函數(shù)傳入零個參數(shù)進行調(diào)用的時候?qū){(diào)用這個fn函數(shù)。由于此時這個ninja是一個新的對象,所以不必?fù)?dān)心之前創(chuàng)建過的方法。
第二次調(diào)用addMethod的時候,首先將之前的同名函數(shù)保存到一個變量old中,然后將新創(chuàng)建的匿名函數(shù)作為方法。新方法首先檢查傳入的個數(shù)是否為1,如果是則調(diào)用新傳入的fn,如果不是,則調(diào)用舊的。重新調(diào)用該函數(shù)的時候?qū)⒃诖藱z查參數(shù)個數(shù)是否為0
這種調(diào)用方式類似于剝洋蔥,每一層都檢查參數(shù)個數(shù)是否匹配。這里的一個技巧是關(guān)于內(nèi)部匿名函數(shù)是否合訪問到old和fn的。這個關(guān)于函數(shù)閉包的知識就在下一篇博客講解(關(guān)注微信公眾號吧)
function addMethod(object,name,fn){ var old = object[name]; object[name] = function(){ if(fn.length === arguments.length){ return fn.apply(this,arguments); }else if(typeof old == "function"){ return old.apply(this,arguments); } } } var ninjas = { values:["Neal","yang","Nealyang","Neal yang"] } addMethod(ninjas,"find",function(){ return this.values; }); addMethod(ninjas,"find",function(name){ var ret = []; for(var i = 0;i關(guān)于上面使用的閉包想關(guān)注的知識,將在下一篇博客中,為大家總結(jié)。
然后使用如上的技巧的時候需要注意下面幾點:
重載是適用于不同數(shù)量的參數(shù),不區(qū)分類型、參數(shù)名稱或者其他東西
這樣的重載方法會有一些函數(shù)調(diào)用的開銷。我們要考慮在高性能時的情況。
交流掃碼關(guān)注我的個人微信公眾號,分享更多原創(chuàng)文章。點擊交流學(xué)習(xí)加我微信、qq群。一起學(xué)習(xí),一起進步
歡迎兄弟們加入:
Node.js技術(shù)交流群:209530601
React技術(shù)棧:398240621
前端技術(shù)雜談:604953717 (新建)
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/92000.html
摘要:請記住,這些書中的一些可能不是最新的,但概念和基礎(chǔ)仍應(yīng)適用。是最好的老師之一。的秘密由部分組成。在你完成這些書后,查看書籍和最好的本土?xí)? 我看過三本,第1本,第二本,第四本。第一本買的的實體書,其他兩本看的是電子書。第一本是大名鼎鼎老道寫的,書很薄,但是非常經(jīng)典。javascirpt忍者秘籍是jquery的作者寫的,也是非常經(jīng)典。you dont kown js系列也是非常好。看了...
摘要:智能手機剛剛普及時,水果忍者這款小游戲可謂風(fēng)靡一時。幾年過去了,現(xiàn)在,讓我們用純來實現(xiàn)這個水果忍者游戲,就算是為了錘煉我們的開發(fā)技能吧。那么只需要修改函數(shù),如下圖的紅色分支就是切到水果的分支,執(zhí)行加分和顯示水果被切成兩半的效果。 智能手機剛剛普及時,水果忍者這款小游戲可謂風(fēng)靡一時。幾年過去了,現(xiàn)在,讓我們用純JavaScript來實現(xiàn)這個水果忍者游戲,就算是為了錘煉我們的JavaScr...
摘要:無處不在的理解語言與其他主流語言相比,函數(shù)式語言的血統(tǒng)更多一些。函數(shù)式語言一類程序設(shè)計語言,是一種非馮諾伊曼式的程序設(shè)計語言。函數(shù)式語言主要成分是原始函數(shù),定義函數(shù)和函數(shù)型。性能分析內(nèi)置對象上的和方法。 無處不在的JavaScript 理解JavaScript語言 與其他主流語言相比,JavaScript函數(shù)式語言的血統(tǒng)更多一些。 函數(shù)式語言一類程序設(shè)計語言,是一種非馮.諾伊曼式的程序...
摘要:閉包閉包的特點就是內(nèi)部匿名函數(shù)可以訪問外部函數(shù)作用域的變量和方法變量對象。閉包的主要表現(xiàn)形式就是匿名函數(shù),但是兩者并不是等價的。中是沒有塊級作用域的,為了在中引入塊級作用域,可以使用匿名函數(shù)模擬塊級作用域。 在介紹閉包之前,首先解釋在隨后的測試實例中會使用的assert測試函數(shù),這個方法有別于alert()測試,有很大的改進。 assert()測試方法 #...
閱讀 2639·2021-11-25 09:43
閱讀 2743·2021-11-04 16:09
閱讀 1660·2021-10-12 10:13
閱讀 891·2021-09-29 09:35
閱讀 894·2021-08-03 14:03
閱讀 1786·2019-08-30 15:55
閱讀 3003·2019-08-28 18:14
閱讀 3502·2019-08-26 13:43