博客原文地址:Claiyre的我的博客
如需轉載,請在文章開頭註明原文地址git
在JavaScript中,函數的功能十分強大。它們是第一類對象,也能夠做爲另外一個對象的方法,還能夠做爲參數傳入另外一個函數,不只如此,還能被一個函數返回!能夠說,在JS中,函數無處不在,無所不能,堪比孫猴子呀!當你運用好函數時,它能助你取西經,讓代碼變得優雅簡潔,運用很差時,那就遭殃了,要大鬧天宮咯~
除了函數相關的基礎知識外,掌握一些高級函數並應用起來,不只能讓JS代碼看起來更爲精簡,還能夠提高性能。如下是博主總結的一些經常使用的、重要的高級函數,加上了一些我的看法,特此記錄下來。若是您是JS初學者,也不要被「高級」兩個字嚇到,由於文中穿插講解了一些原型、this等基礎知識,相信並不難理解。若是您是JS大牛,也能夠把本文用來查漏補缺。github
function Person(name,age){ this.name = name; this.age = age; } var p1 = new Person("Claiyre",80);
相信您對上面的構造函數必定不陌生,可是,,若是某個粗心的程序猿調用這個構造函數時忘記加new
了會發生什麼?瀏覽器
var p3 = Person("Tom",30); console.log(p3); //undefined console.log(window.name); //Tom
因爲使用了不安全的構造函數,上面的代碼意外的改變了window的name,由於this
對象是在運行時綁定的,使用new調用構造函數時this
是指向新建立的對象的,不使用new
時,this
是指向window的。
因爲window的name屬性是用來識別連接目標和frame的,所在這裏對該屬性的偶然覆蓋可能致使其餘錯誤。安全
做用域安全的構造函數會首先確認this
對象是正確類型的實例,而後再進行更改,以下:閉包
function Person(name,age){ if(this instanceof Person){ this.name = name; this.age = age; } else { return new Person(name,age); } }
這樣就避免了在全局對象上意外更改或設置屬性。
實現這個安全模式,至關於鎖定了調用構造函數的環境,所以借用構造函數繼承模式可能會出現問題,解決方法是組合使用原型鏈和構造函數模式,即組合繼承。
若是您是一個JS庫或框架的開發者,相信做用域安全的構造函數必定對您很是有用。在多人協做的項目中,爲了不他們誤改了全局對象,也應使用做用域安全的構造函數。app
因爲瀏覽器間的行爲差別,代碼中可能會有許多檢測瀏覽器行爲的if語句。但用戶的瀏覽器若支持某一特性,便會一直支持,因此這些if語句,只用被執行一次,即使只有一個if語句的代碼,也比沒有要快。
惰性載入表示函數執行的分支僅會執行一次,有兩種實現惰性載入的方式,第一種就是在函數第一次被調用時再處理函數,用檢測到的結果重寫原函數。框架
function detection(){ if(//支持某特性){ detection = function(){ //直接用支持的特性 } } else if(//支持第二種特性){ detection = function(){ //用第二種特性 } } else { detection = function(){ //用其餘解決方案 } } }
第二種實現惰性載入的方式是在聲明函數時就指定適當的函數函數
var detection = (function(){ if(//支持某特性){ return function(){ //直接用支持的特性 } } else if(//支持第二種特性){ return function(){ //用第二種特性 } } else { return function(){ //用其餘解決方案 } } })();
惰性載入函數的有點是在只初次執行時犧牲一點性能,以後便不會再有多餘的消耗性能。性能
在JS中,函數的做用域是在函數被調用時動態綁定的,也就是說函數的this對象的指向是不定的,但在一些狀況下,咱們須要讓某一函數的執行做用域固定,老是指向某一對象。這時怎麼辦呢?
噹噹噹~~能夠用函數綁定做用域函數呀this
function bind(fn,context){ return function(){ return fn.apply(context,arguments); } }
用法:
var person1 = { name: "claiyre", sayName: function(){ alert(this.name); } } var sayPerson1Name = bind(person1.sayName,person1); sayPerson1Name(); //claiyre
call
函數和apply
函數能夠臨時改變函數的做用域,使用bind函數能夠獲得一個綁定了做用域的函數
curry的概念很簡單:只傳遞部分參數來調用函數,而後讓函數返回另外一個函數去處理剩下的參數。能夠理解爲賦予了函數「加載」的能力。
許多js庫中都封裝了curry函數,具體使用能夠這樣。
var match = curry(function(what,str){ return str.match(what) }); var hasNumber = match(/[0-9]+/g); var hasSpace = match(/\s+/g) hasNumber("123asd"); //['123'] hasNumber("hello world!"); //null hasSpace("hello world!"); //[' ']; hasSpace("hello"); //null console.log(match(/\s+/g,'i am Claiyre')); //直接所有傳參也可: [' ',' ']
一旦函數通過柯里化,咱們就能夠先傳遞部分參數調用它,而後獲得一個更具體的函數。這個更具體的函數經過閉包幫咱們記住了第一次傳遞的參數,最後咱們就能夠用這個更具體的函數隨心所欲啦~
一個較爲簡單的實現curry的方式:
function curry(fn){ var i = 0; var outer = Array.prototype.slice.call(arguments,1); var len = fn.length; return function(){ var inner = outer.concat(Array.prototype.slice.call(arguments)); return inner.length === len?fn.apply(null,inner):function (){ var finalArgs = inner.concat(Array.prototype.slice.call(arguments)); return fn.apply(null,finalArgs); } } }
debounce函數,又稱「去抖函數」。它的功能也很簡單直接,就是防止某一函數被連續調用,從而致使瀏覽器卡死或崩潰。用法以下:
var myFunc = debounce(function(){ //繁重、耗性能的操做 },250); window.addEventListener('resize',myFunc);
像窗口的resize,這類能夠以較高的速率觸發的事件,很是適合用去抖函數,這時也可稱做「函數節流」,避免給瀏覽器帶來過大的性能負擔。
具體的實現時,當函數被調用時,不當即執行相應的語句,而是等待固定的時間w,若在w時間內,即等待還未結束時,函數又被調用了一次,則再等待w時間,重複上述過程,直到最後一次被調用後的w時間內該函數都沒有被再調用,則執行相應的代碼。
實現代碼以下:
function debounce(fn,wait){ var td; return function(){ clearTimeout(td); td= setTimeout(fn,wait); } }
顧名思義,once函數是僅僅會被執行一次的函數。具體實現以下:
function once(fn){ var result; return function(){ if(fn){ result = fn(arguments); fn = null; } return result; } } var init = once(function(){ //初始化操做 })
在被執行過一次後,參數fn就被賦值null了,那麼在接下來被調用時,便不再會進入到if語句中了,也就是第一次被調用後,該函數永遠不會被執行了。
還能夠對上述once函數進行改進,不只能夠傳入函數,同時還能夠給傳入的函數綁定做用域u,同時實現了bind和once。
function once(fn,context){ var result; return function(){ if(fn){ result = fn.apply(context,arguments); fn = null; } return result; } }
經過以上的閱讀,不難發現不少「高級函數」的實現其實並不複雜,數十行代碼即可搞定,但重要的是能真正理解它們的原理,在實際中適時地應用,以此性能提高,讓代碼簡潔,邏輯清晰