也可以基於這一思路測試某個值是不是原生函數或正則表達式: 方法2、在聲明函數時指定適當的函數,這樣第一次調用函數不損失性能,在代碼首次載入時會損失性能 按下按鈕實際顯示的是undefined,並不會顯示Event handled。問題在於沒有保存handler.handleClick的環境,所以th
第22章,高級技巧
高級函數
安全的類型檢測
typeof會出現無法預知的行為 instanceof在多個全局作用域中並不能正確工作 調用Object原生的toString方法,會返回[Object NativeConstructorName]格式字元串。每個類內部都有一個[[Class]]屬性,這個屬性中就指定了上述字元串中的構造函數名。 原生數組的構造函數名與全局作用域無關,因此使用toString方法能保證返回一致的值,為此可以創建如下函數:function isArray(value){ return Object.prototype.toString.call(value) == "[object Array]"; }
也可以基於這一思路測試某個值是不是原生函數或正則表達式:
//判斷是否原生函數 function isFunction(value){ return Object.prototype.toString.call(value) == "[object Function]"; } //判斷是否原生函數 function isFunction(value){ return Object.prototype.toString.call(value) == "[object RegExp]"; }註:對於IE中以COM對象形式實現的任何函數,isFunction都將返回false,因為他們並不是js原生函數 註:Object的toString方法不能檢測非原生構造函數的函數名。
作用域安全的構造函數
對於構造函數,使用了new操作符,則首先創建一個新的對象,將this指針指向新創建的對象,如果沒有使用new操作符,則this指針會指向window對象,作用域安全的構造函數:function Person(name,age,job){ if(this instanceof Person){ //判斷this是否是正確的類型 this.name = name; this.age = age; this.job = job; }else{ return new Person(name,age,job); } } var per1 = Person("Nicholas",29,"Software Engineer"); alert(window.name); //"" alert(per1.name); //"Nicholas" var per2 = new Person("Shelby",34,"Ergonomist"); alert(per2.name); //"Shelby"建議:推薦使用作用域安全的構造函數作為最佳實踐
惰性載入函數
對於多分支的if語句,有些時候並不需要每次都查詢if語句,例如createXHR函數:function createXHR(){ if(typeof XMLHttpRequest != "undefined"){ return new XMLHttpRequest; }else if(typeof ActiveXObject != "undefined"){ if(typeof arguments.callee.activeXString != "string"){ var versions = ["MSXML2.XMLHttp.6.0","MSXML2.XMLHttp.3.0","MSXML2.XMLHttp"],i,len; for(i=0,len=versions.length;i < len;i++){ try{ new ActiveXObject(versions[i]); arguments.callee.activeXString = versions[i]; break; }catch(ex){ //跳過 } } } return new ActiveXObject(arguments.callee.activeXString); }else{ throw new Error("No XHR Object available"); } }需要每次都對瀏覽器進行檢測,根本沒有必要,只要檢測一次就足夠了。對於此情況,解決方案:惰性載入技巧 惰性載入表示函數分支只會執行一次。 方法1、在函數被調用時處理函數,在第一次調用時該函數會被覆蓋為另一個按合適方式執行的函數。
function createXHR(){ if(typeof XMLHttpRequest != "undefined"){ createXHR = function(){ //將原函數覆蓋 return new XMLHttpRequest(); }; }else if(typeof ActiveXObject != "undefined"){ createXHR = function(){ //將原函數覆蓋 if(typeof arguments.callee.activeXString != "string"){ var versions = ["MSXML2.XMLHttp.6.0","MSXML2.XMLHttp.3.0","MSXML2.XMLHttp"],i,len; for(i=0,len=versions.length;i < len;i++){ try{ new ActiveXObject(versions[i]); arguments.callee.activeXString = versions[i]; break; }catch(ex){ //跳過 } } } return new ActiveXObject(arguments.callee.activeXString); }; }else{ createXHR = function(){ //將原函數覆蓋 throw new Error("No XHR object available."); }; } return createXHR(); }
方法2、在聲明函數時指定適當的函數,這樣第一次調用函數不損失性能,在代碼首次載入時會損失性能
var createXHR = (function(){ if(typeof XMLHttpRequest != "undefined"){ return function(){ return new XMLHttpRequest(); }; }else if(typeof ActiveXObject != "undefined"){ return function(){ if(typeof arguments.callee.activeXString != "string"){ var versions = ["MSXML2.XMLHttp.6.0","MSXML2.XMLHttp.3.0","MSXML2.XMLHttp"],i,len; for(i=0,len=versions.length;i < len;i++){ try{ new ActiveXObject(versions[i]); arguments.callee.activeXString = versions[i]; break; }catch(ex){ //跳過 } } } return new ActiveXObject(arguments.callee.activeXString); }; }else{ return function(){ throw new Error("No XHR object available."); }; } })();
函數綁定
函數綁定要創建一個函數,可以在特定的this環境中以指定參數調用另一個函數。該技巧常常和回調函數與事件處理程式一起使用。var handler = { message : "Event handled", handleClick : function(event){ alert(this.message); } }; var btn = document.getElementById("my-btn"); EventUtil.addHandler(btn,"click",handler.handleClick); //按鈕按下顯示undefined,不顯示Event handled
按下按鈕實際顯示的是undefined,並不會顯示Event handled。問題在於沒有保存handler.handleClick的環境,所以this最後指向了DOM按鈕,而非handler(IE8中this指向window),可以使用閉包解決問題:
var handler = { message : "Event handled", handleClick : function(event){ alert(this.message); } }; var btn = document.getElementById("my-btn"); EventUtil.addHandler(btn,"click",function(event){ handler.handleClick(event); //按鈕按下顯示Event handled });問題:閉包難以理解和調試。 大部分js庫為此實現了可以將函數綁定到指定環境的函數,一般叫做bind() 簡單的bind函數,接收一個函數和環境,返回一個給定函數中調用給定函數的函數,並且將所有參數原封不動傳過去
//bind函數解決方案 function bind(fn,context){ return function(){ return fn.apply(context,arguments); }; }
調用方法:
EventUtil.addHandler(btn,"click",bind(handler.handleClick,handler)); ECMAScript5為所有函數定義了原生的bind函數,進一步簡化了操作。調用方法 EventUtil.addHandler(btn,"click",handler.handleClick.bind(handler)); //傳入作為this的對象
支持的瀏覽器:IE9+,Firefox4+,Chrome
函數柯里化
用於創建已經設置好了一個或多個參數的函數。基本方法和函數綁定一樣:使用閉包返回一個函數。區別:函數被調用時返回的函數還需要設置一些傳入參數。防篡改對象
註意:一旦把對象定義為防篡改對象,將不可撤銷,跟定義了屬性的[[Configurable]]為false的結果差不多不可擴展對象
使用Object.preventExtensions()方法,讓你不能再給對象添加屬性和方法var person = { name : "name"}; Object.preventExtensions(person); person.age = 28; alert(person.age); //undefined Object.isExtensible可檢測對象是否可擴展
密封對象
密封對象不可擴展,而且已有成員的[[Configurable]]特性將被設置為false。意味著不能修改或刪除屬性和方法 使用Object.seal()方法密封對象,使用Object.isSealed()方法確定對象是否密封。var person = { name : "name" }; alert(Object.isExtensible(person)); //true alert(Object.isSealed(person)); //false Object.seal(person); alert(Object.isExtensible(person)); //false alert(Object.isSealed(person)); //true
凍結對象
最嚴格防篡改級別。凍結對象既不可擴展,又是密封,對象的數據屬性的[[Writable]]特性會被設置為false。如果定義了[[Set]]函數,訪問器屬性仍然可寫。 使用Object.freeze()方法凍結對象,Object.isFrozen()方法檢測對象是否凍結。高級定時器
js是單線程的,定時器僅僅只是計劃代碼在未來某個時間執行,執行時間並不確定。意思就是,js是單線程的,所有需要處理的代碼都要排到執行隊列中去,而定時器,只是在我們設定的時間之後將代碼添加到隊列中去,並不一定馬上執行代碼。重覆定時器
setInterval定時器的功能缺陷:可能在代碼再次被添加到隊列中時,之前的代碼還沒有完成執行,結果導致代碼運行好幾次。js引擎足夠聰明,能避免這個問題,在使用setInterval時,僅當沒有定時器的任何其他代碼實例時,才將定時器代碼加入到隊列。不過,還是有問題,1、某些間隔會被跳過,多個定時器的代碼執行之間的間隔可能比預期小。
函數節流
函數節流背後的基本思想是指,某些代碼不可以在沒有間斷的情況連續重覆執行。第一次調用函數,創建一個定時器,在指定時間間隔之後運行代碼。當第二次調用函數時,會清除前一次的定時器並設置另一個,如果前一個已經執行過了,此操作無意義。然而,若前一個定時器尚未執行,其實就是將其替換為一個新的定時器。目的在於只有執行函數的請求停止了一段時間後才執行。自定義事件
拖放
第24章,最佳實踐
可維護性
特性:
1、可理解 2、直觀 3、可適應 4、可擴展 5、可調試代碼約定
1、可讀性
(1)使用若幹空格而非製表符進行縮進 (2)註釋 函數和方法:每個函數或方法都應該包含一個註釋,用於描述目的和用於完成任務所可能使用的演算法,參數意義,返回值 大段代碼:描述任務的註釋 複雜演算法:解釋如何做的註釋 Hack:因瀏覽器差異,js代碼通常會包含一些hack,這些需要註釋。2、變數和函數命名
(1)變數名應為名詞 (2)函數名應以動詞開頭,getName(),返回布爾值的函數以is開頭,isEnable() (3)使用合乎邏輯的名字,不必擔心長度,長度問題可以使用後處理和壓縮。3、變數類型透明
很容易忘記變數所應包含的數據類型。合適的命名方式可以一定程度上緩解問題。還有以下三種表示變數數據類型的方式。 (1)變數初始化 (2)使用匈牙利標記法來指定變數類型。即,在變數前加一個或多個字元表示變數類型。 (3)使用變數註釋var found /*Boolean*/ = false;
鬆散耦合
1、解耦HTML/JavaScript
理想情況:HTML和JavaScript應完全分離,使用外部文件和DOM附加行為來包含JavaScript2、解耦CSS/JavaScript
//CSS對於JavaScript的緊密耦合 element.style.color = "red"; element.style.backgroundColor = "blue"; //CSS對於JavaScript的鬆散耦合 elements.className = "edit";
3、解耦應用邏輯/事件處理程式
編程實踐
1、尊重對象所有權
意思是,不能修改不屬於自己的對象。就是說,不是自己創建或維護的對象,就不要更改它的屬性和方法,即 (1)不要為實例或原型添加屬性 (2)不要為實例或原型添加方法 (3)不要重定義已存在的方法2、避免全局變數
最多創建一個全局變數3、避免與null進行比較
4、使用常量
var Constants = { INVALID_VALUE_MSG:"Invalid value!", INVALID_VALUE_URL:"/errors/invalid.php" }; function validate(value){ if(!value){ alert(Constants.INVALID_VALUE_MSG); location.href = Constants.INVALID_VALUE_URL; } }
性能
註意作用域
1、避免全局查找 2、避免with語句選擇正確方法
1、避免不必要的屬性查找 2、優化迴圈 (1)減值迭代在大多數情況下更高效,即迴圈從最大值開始 (2)簡化終止條件 (3)簡化迴圈體 (4)使用後測試迴圈,do-while迴圈 3、展開迴圈 當迴圈次數是確定的,消除迴圈並使用多次函數調用往往會更快。 Duff裝置。 4、避免雙重解釋最小化語句
1、多變數聲明
//4個語句---浪費 var count = 5; var color = "red"; var values = [1,2,3]; var now = new Date(); //一個語句 var count = 5; color = "red"; values = [1,2,3]; now = new Date();
2、插入迭代值
var name = values[i]; i++; //合併 var name = values[i++];