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