前面的話
函數對任何一門語言來說都是一個核心的概念,在javascript中更是如此。前面曾以深入理解函數系列的形式介紹了函數的相關內容,本文將再深入一步,介紹函數的3個高級技巧
技巧一:作用域安全的構造函數
構造函數其實就是一個使用new操作符調用的函數
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);//undefined console.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節點添加事件的函數
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語句不必每次都執行,代碼可以運行的更快一些。
解決方案就是惰性載入。所謂惰性載入,指函數執行的分支只會發生一次
有兩種實現惰性載入的方式
【1】第一種是在函數被調用時,再處理函數。函數在第一次調用時,該函數會被覆蓋為另外一個按合適方式執行的函數,這樣任何對原函數的調用都不用再經過執行的分支了
我們可以用下面的方式使用惰性載入重寫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語句了
但是,這種方法有個缺點,如果函數名稱有所改變,修改起來比較麻煩
【2】第二種是聲明函數時就指定適當的函數。 這樣在第一次調用函數時就不會損失性能了,只在代碼加載時會損失一點性能
以下就是按照這一思路重寫的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()。然而,被綁定函數與普通函數相比有更多的開銷,它們需要更多內存,同時也因為多重函數調用稍微慢一點,所以最好只在必要時使用。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持。