函數對任何一門語言來講都是一個核心的概念,在javascript中更是如此。本文將介紹函數的5個高級技巧javascript
做用域安全的構造函數前端
構造函數其實就是一個使用new操做符調用的函數java
function Person(name,age,job){ this.name=name; this.age=age; this.job=job; }var person=new Person('match',28,'Software Engineer'); console.log(person.name);//match
若是沒有使用new操做符,本來針對Person對象的三個屬性被添加到window對象算法
function Person(name,age,job){ this.name=name; this.age=age; this.job=job; } var person=Person('match',28,'Software Engineer'); console.log(person);//undefinedconsole.log(window.name);//match
window的name屬性是用來標識連接目標和框架的,這裏對該屬性的偶然覆蓋可能會致使頁面上的其它錯誤,這個問題的解決方法就是建立一個做用域安全的構造函數數組
function Person(name,age,job){ if(this instanceof Person){ this.name=name; this.age=age; this.job=job; }else{ return new Person(name,age,job); } }var person=Person('match',28,'Software Engineer'); console.log(window.name); // ""console.log(person.name); //'match'var person= new Person('match',28,'Software Engineer'); console.log(window.name); // ""console.log(person.name); //'match'
可是,對構造函數竊取模式的繼承,會帶來反作用。這是由於,下列代碼中,this對象並不是Polygon對象實例,因此構造函數Polygon()會建立並返回一個新的實例瀏覽器
function Polygon(sides){ if(this instanceof Polygon){ this.sides=sides; this.getArea=function(){ return 0; } }else{ return new Polygon(sides); } }function Rectangle(wifth,height){ Polygon.call(this,2); this.width=this.width; this.height=height; this.getArea=function(){ return this.width * this.height; }; }var rect= new Rectangle(5,10); console.log(rect.sides); //undefined
若是要使用做用域安全的構造函數竊取模式的話,須要結合原型鏈繼承,重寫Rectangle的prototype屬性,使它的實例也變成Polygon的實例安全
function Polygon(sides){ if(this instanceof Polygon){ this.sides=sides; this.getArea=function(){ return 0; } }else{ return new Polygon(sides); } }function Rectangle(wifth,height){ Polygon.call(this,2); this.width=this.width; this.height=height; this.getArea=function(){ return this.width * this.height; }; } Rectangle.prototype= new Polygon();var rect= new Rectangle(5,10); console.log(rect.sides); //2
惰性載入函數閉包
由於各瀏覽器之間的行爲的差別,咱們常常會在函數中包含了大量的if語句,以檢查瀏覽器特性,解決不一樣瀏覽器的兼容問題。好比,咱們最多見的爲dom節點添加事件的函數app
function addEvent(type, element, fun) { if (element.addEventListener) { element.addEventListener(type, fun, false); } else if(element.attachEvent){ element.attachEvent('on' + type, fun); } else{ element['on' + type] = fun; } }
每次調用addEvent函數的時候,它都要對瀏覽器所支持的能力進行檢查,首先檢查是否支持addEventListener方法,若是不支持,再檢查是否支持attachEvent方法,若是還不支持,就用dom0級的方法添加事件。這個過程,在addEvent函數每次調用的時候都要走一遍,其實,若是瀏覽器支持其中的一種方法,那麼他就會一直支持了,就沒有必要再進行其餘分支的檢測了。也就是說,if語句沒必要每次都執行,代碼能夠運行的更快一些。框架
解決方案就是惰性載入。所謂惰性載入,指函數執行的分支只會發生一次,有兩種實現惰性載入的方式
一、第一種是在函數被調用時,再處理函數。函數在第一次調用時,該函數會被覆蓋爲另一個按合適方式執行的函數,這樣任何對原函數的調用都不用再通過執行的分支了
咱們能夠用下面的方式使用惰性載入重寫addEvent()
function addEvent(type, element, fun) { if (element.addEventListener) { addEvent = function (type, element, fun) { element.addEventListener(type, fun, false); } } else if(element.attachEvent){ addEvent = function (type, element, fun) { element.attachEvent('on' + type, fun); } } else{ addEvent = function (type, element, fun) { element['on' + type] = fun; } } return addEvent(type, element, fun); }
在這個惰性載入的addEvent()中,if語句的每一個分支都會爲addEvent變量賦值,有效覆蓋了原函數。最後一步即是調用了新賦函數。下一次調用addEvent()時,便會直接調用新賦值的函數,這樣就不用再執行if語句了
可是,這種方法有個缺點,若是函數名稱有所改變,修改起來比較麻煩
二、第二種是聲明函數時就指定適當的函數。 這樣在第一次調用函數時就不會損失性能了,只在代碼加載時會損失一點性能
如下就是按照這一思路重寫的addEvent()。如下代碼建立了一個匿名的自執行函數,經過不一樣的分支以肯定應該使用哪一個函數實現
var addEvent = (function () { if (document.addEventListener) { return function (type, element, fun) { element.addEventListener(type, fun, false); } } else if (document.attachEvent) { return function (type, element, fun) { element.attachEvent('on' + type, fun); } } else { return function (type, element, fun) { element['on' + type] = fun; } } })();
函數綁定
在javascript與DOM交互中常常須要使用函數綁定,定義一個函數而後將其綁定到特定DOM元素或集合的某個事件觸發程序上,綁定函數常常和回調函數及事件處理程序一塊兒使用,以便把函數做爲變量傳遞的同時保留代碼執行環境
<button id="btn">按鈕</button><script> var handler={ message:"Event handled.", handlerFun:function(){ alert(this.message); } }; btn.onclick = handler.handlerFun;</script>
上面的代碼建立了一個叫作handler的對象。handler.handlerFun()方法被分配爲一個DOM按鈕的事件處理程序。當按下該按鈕時,就調用該函數,顯示一個警告框。雖然貌似警告框應該顯示Event handled,然而實際上顯示的是undefiend。這個問題在於沒有保存handler.handleClick()的環境,因此this對象最後是指向了DOM按鈕而非handler
可使用閉包來修正這個問題
<button id="btn">按鈕</button><script> var handler={ message:"Event handled.", handlerFun:function(){ alert(this.message); } }; btn.onclick = function(){ handler.handlerFun(); }</script>
固然這是特定於此場景的解決方案,建立多個閉包可能會令代碼難以理解和調試。更好的辦法是使用函數綁定
一個簡單的綁定函數bind()接受一個函數和一個環境,並返回一個在給定環境中調用給定函數的函數,而且將全部參數原封不動傳遞過去
function bind(fn,context){ return function(){ return fn.apply(context,arguments); } }
這個函數彷佛簡單,但其功能是很是強大的。在bind()中建立了一個閉包,閉包使用apply()調用傳入的函數,並給apply()傳遞context對象和參數。當調用返回的函數時,它會在給定環境中執行被傳入的函數並給出全部參數
<button id="btn">按鈕</button><script> function bind(fn,context){ return function(){ return fn.apply(context,arguments); } } var handler={ message:"Event handled.", handlerFun:function(){ alert(this.message); } }; btn.onclick = bind(handler.handlerFun,handler);</script>
ECMAScript5爲全部函數定義了一個原生的bind()方法,進一步簡化了操做
只要是將某個函數指針以值的形式進行傳遞,同時該函數必須在特定環境中執行,被綁定函數的效用就突顯出來了。它們主要用於事件處理程序以及setTimeout()和setInterval()。然而,被綁定函數與普通函數相比有更多的開銷,它們須要更多內存,同時也由於多重函數調用稍微慢一點,因此最好只在必要時使用
函數柯里化
與函數綁定緊密相關的主題是函數柯里化(function currying),它用於建立已經設置好了一個或多個參數的函數。函數柯里化的基本方法和函數綁定是同樣的:使用一個閉包返回一個函數。二者的區別在於,當函數被調用時,返回的函數還須要設置一些傳入的參數
function add(num1,num2){ return num1+num2; }function curriedAdd(num2){ return add(5,num2); } console.log(add(2,3));//5console.log(curriedAdd(3));//8
這段代碼定義了兩個函數:add()和curriedAdd()。後者本質上是在任何狀況下第一個參數爲5的add()版本。儘管從技術來講curriedAdd()並不是柯里化的函數,但它很好地展現了其概念
柯里化函數一般由如下步驟動態建立:調用另外一個函數併爲它傳入要柯里化的函數和必要參數。下面是建立柯里化函數的通用方式
function curry(fn){ var args = Array.prototype.slice.call(arguments, 1); return function(){ var innerArgs = Array.prototype.slice.call(arguments), finalArgs = args.concat(innerArgs); return fn.apply(null, finalArgs); }; }
curry()函數的主要工做就是將被返回函數的參數進行排序。curry()的第一個參數是要進行柯里化的函數,其餘參數是要傳入的值。爲了獲取第一個參數以後的全部參數,在arguments對象上調用了slice()方法,並傳入參數1表示被返回的數組包含從第二個參數開始的全部參數。而後args數組包含了來自外部函數的參數。在內部函數中,建立了innerArgs數組用來存放全部傳入的參數(又一次用到了slice())。有了存放來自外部函數和內部函數的參數數組後,就可使用concat()方法將它們組合爲finalArgs,而後使用apply()將結果傳遞給函數。注意這個函數並無考慮到執行環境,因此調用apply()時第一個參數是null。curry()函數能夠按如下方式應用
function add(num1, num2){ return num1 + num2; }var curriedAdd = curry(add, 5); alert(curriedAdd(3)); //8
在這個例子中,建立了第一個參數綁定爲5的add()的柯里化版本。當調用cuurriedAdd()並傳入3時,3會成爲add()的第二個參數,同時第一個參數依然是5,最後結果即是和8。也能夠像下例這樣給出全部的函數參數:
function add(num1, num2){ return num1 + num2; }var curriedAdd2 = curry(add, 5, 12); alert(curriedAdd2()); //17
在這裏,柯里化的add()函數兩個參數都提供了,因此之後就無需再傳遞給它們了
函數柯里化還經常做爲函數綁定的一部分包含在其中,構造出更爲複雜的bind()函數
function bind(fn, context){ var args = Array.prototype.slice.call(arguments, 2); return function(){ var innerArgs = Array.prototype.slice.call(arguments), finalArgs = args.concat(innerArgs); return fn.apply(context, finalArgs); }; }
對curry()函數的主要更改在於傳入的參數個數,以及它如何影響代碼的結果。curry()僅僅接受一個要包裹的函數做爲參數,而bind()同時接受函數和一個object對象。這表示給被綁定的函數的參數是從第三個開始而不是第二個,這就要更改slice()的第一處調用。另外一處更改是在倒數第3行將object對象傳給apply()。當使用bind()時,它會返回綁定到給定環境的函數,而且可能它其中某些函數參數已經被設好。要想除了event對象再額外給事件處理程序傳遞參數時,這很是有用
var handler = { message: "Event handled", handleClick: function(name, event){ alert(this.message + ":" + name + ":" + event.type); } };var btn = document.getElementById("my-btn"); EventUtil.addHandler(btn, "click", bind(handler.handleClick, handler, "my-btn"));
handler.handleClick()方法接受了兩個參數:要處理的元素的名字和event對象。做爲第三個參數傳遞給bind()函數的名字,又被傳遞給了handler.handleClick(),而handler.handleClick()也會同時接收到event對象
ECMAScript5的bind()方法也實現函數柯里化,只要在this的值以後再傳入另外一個參數便可
var handler = { message: "Event handled", handleClick: function(name, event){ alert(this.message + ":" + name + ":" + event.type); } };var btn = document.getElementById("my-btn"); EventUtil.addHandler(btn, "click", handler.handleClick.bind(handler, "my-btn"));
javaScript中的柯里化函數和綁定函數提供了強大的動態函數建立功能。使用bind()仍是curry()要根據是否須要object對象響應來決定。它們都能用於建立複雜的算法和功能,固然二者都不該濫用,由於每一個函數都會帶來額外的開銷
函數重寫
因爲一個函數能夠返回另外一個函數,所以能夠用新的函數來覆蓋舊的函數
function a(){ console.log('a'); a = function(){ console.log('b'); } }
這樣一來,當咱們第一次調用該函數時會console.log('a')會被執行;全局變量a被重定義,並被賦予新的函數
當該函數再次被調用時, console.log('b')會被執行
再複雜一點的狀況以下所示
var a = (function(){ function someSetup(){ var setup = 'done'; } function actualWork(){ console.log('work'); } someSetup(); return actualWork; })()
咱們使用了私有函數someSetup()和actualWork(),當函數a()第一次被調用時,它會調用someSetup(),並返回函數actualWork()的引用
前端技術分享點擊:加入