現在隨着javascript日趨流行,編寫可維護的js也變得重要,根據工作中遇到的問題,今天寫下這遍文章供大家參考(會持續更新),大家如有問題可以溝通!
1,正確的檢測數據類型
(1)大家都知道typeof可以返回一個用於識別其運算數類型的字符串;對於任何變量來說,使用typeof可以返回以下6中類型之一:
number,string,boolean;object;function;undefined;
不過,對於null來說,typeof返回 的卻是object;因此我們進行簡單封裝下:
function type(o){
return (o===null) ? 'null' : typeof o;
}
但是,一定要注意,typeof不能檢測複雜的數據類型,以及各種特殊的用途的對象;如正則表達式對象;date對象;數學對象等;
如果,對於對象或數組,我們可以用constructor屬性,該屬性值引用的是原來構造該對象的函數,如果結合typeof和constructor,基本上可以檢測數據類型;
(2)當然,使用constructor可以判斷大部分的數據類型,但是對於null和undefined特殊值,就不能使用constrictor,因爲使用javascript解釋器會跑出異常,此時可以,先把值轉爲布爾值,如果爲true,則說明不是null和undefined;然後再調用constructor
var val = undefined;
console.log(typeof val);//"undefined"
console.log(val && val.constructor);//undefined
var vals = null;
console.log(typeof vals);//object
console.log(vals && vals.constructor);//null
對於數值直接量,也不能直接使用constrctor,不過可以用小括號括起來,因爲小括號運算符可以把樹枝轉換爲對象;
console.log((10).constructor);//function Number() { [native code] }
(3)使用toString()方法檢測對象類型是最安全/最準確的;調用其方法,把對象轉換爲字符串,然後通過檢測字符串中是否包含數組所特有的標誌可以確定對象的數據類型;
其方法返回的字符串如下:
【object class】
其中objce是對象的通用類型。class表示對象內部類型,內部類型的名稱與該對象的構造函數名對應;例如Array對象的class是“Array”;
但是,要獲取對象的class值的唯一方法是調用Obect對象定義的toString()方法,因爲不同對象都會預定一自己的toString(),所以不能直接調用,例如:
var data = new Date();
console.log(data.toString());//返回utc時間
要調用Object的toString()方法,可以先調用Object.prototype.toString對象的默認的toString()方法,再調用該函數的apply()方法在想要檢測的對象上執行;
var data = new Date();
console.log(Object.prototype.toString.apply(data));//[object Date]
下面是一個完整的檢測數據類型的方法:
function typeOf(o){
var _toString = Object.prototype.toString;
var _type = {
'undefiend':'undefiend',
'number':'number',
'boolean':'boolean',
'string':'string',
'[object Function]':'function',
'[object RegExp]':'regexp',
'[object Array]':'array',
'[object Date]':'date',
'[object Error]':'error'
};
return _type[typeof o] || _type[_toString.call(o)] || (o ? 'object':'null');
}
var a = Math.abs();
console.log(typeOf(a));//number
上面的方法對於自定義的對象是無效的;這是因爲自定義對象轉換字符串後,返回的值時沒有規律的;
2,正確處理javascript特殊值
(1)正確使用NaN和Infinity
爲了方便檢測NaN值,js提供了靜態方法isNaN,以辨數字和NaN的區別;
其實判斷一個值是否可以用數字的最佳方法是使用isFinite函數,因爲它會先刷掉NaN和Infinity(無窮大);使用inFinite函數能夠檢測NaN,正負無窮大;不幸的是,它會試圖把它的運算數轉換爲一個數字,因此,如果值不是一個數字,使用isFinite不是一個有效的檢測方法,不過,我們可以封裝下isNumber函數
var isNumber = function(val){
return typeof val === 'number' && isFinite(val);
};
console.log(isNumber('11'));//false
(2)謹慎使用僞數組
要判斷一個值是否爲數組,必須使用constructor屬性
var value = [];
if(value && typeof value === 'object' && value.constructor === Array){
console.log('true');//true
}else{
console.log('false')
}
3,推薦提高條件性能的策略
if(a){
if(b){
if(c){
if(d){
alert('所有條件都成立!');
}else{
alert('條件d不成立!')
}
}else{
alert('條件c不成立!');
}
}else{
alert('條件b不成立!');
}
}else {
alert('條件a不成立!');
}
從思維方向上來說,這種結構嵌套並沒有錯誤;但是,如果這種多重結構嵌套,就會出現另外一種可能:a條件不成立,直接退出,而不管b,c,d條件是否成立,狠武斷,給測試帶來傷害;未來避免上述情況,我們可以採用排除法,即對每個條件進行判斷,條件成立再執行特定的操作; var flag = true;
if(!a){
alert('條件a不成立');
flag = false;
}
if(!b){
alert('條件b不成立');
flag = false;
}
if(!c){
alert('條件c不成立');
flag = false;
}
if(!d){
alert('條件d不成立');
flag = false;
}
if(flag){
alert('所有條件都成立!');
}
(2)獲取字節長度問題 String.prototype.sumLength = function(){
var _b = 0,_l = this.length;
if(_l){
for(var i= 0;i<_l;i++){
if(this.charCodeAt(i) > 255){
_b +=2;
}else{
_b +=1;
}
}
return _b;
}else{
return 0;
}
}
var s = '我是andy';
console.log(s.sumLength());//8
(3)正確的檢測數組類型 var isArray = function(val){
return Object.prototype.toString.apply(val) === '[object Array]';
}
4,使用arguments模擬重載
function sayHello(){
switch (arguments.length){
case 0:
return 'Hello';
case 1:
return 'Hello,'+arguments[0];
case 2:
return (arguments[1] == 'cn' ? '你好,':'Hello, ') + arguments[0];
}
}
console.log(sayHello());
console.log(sayHello('andy'));
console.log(sayHello('andy','cn'));
4,比較函數調用模式
var obj = {
value:0,
increment:function(inc){
this.value += typeof inc === 'number' ? inc :1;
}
};
obj.increment();
console.log(obj.value);//1
obj.increment(2);
console.log(obj.value);//3
(2)函數調用模式
當函數以次模式調用時,this被綁定到全局對象.這無非是語言設計上的錯誤.當若語言設計正確,當內部函數被調用時, this應該仍綁定到外部函數的this變量.不過,有一個狠容易的解決方法:如果改辦法定義一個變量並將它賦值爲this, 那麼內部函數就可以通過這個變量訪問this
var obj = {
value:1,
doub:function(){
var that = this;
var helper = function(){
that.value = that.value *2;
};
helper();
}
}
obj.doub();
console.log(obj.value);//2
(3)構造器調用模式js是一門基於原型繼承的語言,改語言是無類別的,對象可以直接從其它對象繼承屬性. 當今大多數語言都是基於類的語言,雖然原型繼承有着強大的表現力,但它偏離來主流用法,不被廣泛理解; js爲了能夠兼容基於類語言的編寫風格,提供來一套基於類語言的對象構建語法; 如果在一個函數前面加上new運算符來進行調用,那麼將創建一個隱藏鏈接到該函數的prototype原型對象的新實例對象, 同時this將會被綁定到這個新實例對象上,注意,new前綴也會改變return語句的行爲;
var F = function(string){
this.status = string;
};
F.prototype.getNum = function(){
return this.status;
};
var f = new F('andy');
console.log(f.getNum());//andy
(4)apply調用模式
js是函數式的面向對象編程語言,函數可以擁有方法.apply就是函數的一個基本方法,
使用這個方法可以調用函數,並修改函數體內的this值;
apply方法包括兩個參數:第一個參數設置綁定給this的值;第二個參數是包涵參數的數組;
var array = [5,4];
var add = function(){
var i,sum = 0;
for(i=0;i<arguments.length;i+=1){
sum += arguments[i];
}
return sum;
};
var sum = add.apply({},array);
console.log(sum);//9
上面代碼構建一個包涵兩個數字的數組,然後使用apply方法調用add()函數,將數組array中的元素值相加.
var F = function(string){
this.status = string;
};
F.prototype.get = function(){
return this.status;
};
var obj = {
status :'objh'
};
var status = F.prototype.get.apply(obj);
console.log(status);//objh
上面代碼構建了一個構造函數F,爲該函數定義了一個原型方法get,該方法能夠讀取當前對象當status屬性的值. 然後定義了一個obj對象,改對象包涵了一個status屬性,使用apply方法在obj對象上調用構造函數F的get方法, 返回obj對象的status屬性值
5,使用閉包跨域開發
閉包是指詞法表示包括不必計算的變量的函數,必包函數能夠使用函數外定義的變量;
閉包結構有兩個比較鮮明的特徵:
(1)封閉性
(2)持久性
對於一般函數來說,調用後會註銷掉,而對於閉包來說,在外部函數被調用後,閉包依然保存在系統中,閉包的函數依然存在,
從而實現對數據對持久使用。
function bar(x){
var a = x;
var b = function(){
return a;
};
return b;
}
var c1 = bar(1);
console.log(c1());//1---調用閉包函數
//在上面實例中,首先在函數bar結構體內定義兩個變量,分別存儲參數和必報結構,而閉包結構中
//寄存着參數值.當調用函數bar之後,函數結構被註銷,它對局部變量也會跟着註銷掉,因此變量a中
//存儲的參數值也會隨之丟失.但是由於變量b存儲着必報結構,因此閉包結構內部的參數值並沒有釋放,
//在調用函數之後,依然能夠從閉包中讀取到參數值
function f(x){//外部函數
var a = x;//外部函數的局部變量,並把參數值傳遞給它
var b = function(){//內部函數
return a;//訪問外部函數中的局部變量
};
a++;//訪問後,動態更新外部函數的變量
return b;//內部函數
}
var c = f(5);//調用外部函數,並賦值
console.log(c(5));//調用內部函數,返回外部函數更新後的值6
//如果沒有閉包函數的作用,那麼這種數據寄存和傳遞就無法得以實施:
function f2(x){
var a = x;
var b = a;
a++;
return b;
}
var c2 = f2(5);
console.log(c2);//5
6,推薦鏈式調用方法
在js中,很多方法沒有返回值,一些設置或修改對象的某個狀態卻不返回任何值的方法就是典型的例子; 如果讓這些方法返回this,而不是undefiend,那麼就要啓用級聯功能,即所謂的鏈式語法. 在一個級聯中,單獨一條語句可以連續調用同一個對象的很多方法 如下面擴展String的3個方法
Function.prototype.method = function(name,func){
if(!this.prototype[name]){
this.prototype[name] = func;
return this;
}
};
String.method('trim',function(){
return this.replace(/^\s+|\s+$/g,'');
});
String.method('writeIn',function(){
console.log(this);
return this;
});
String.method('alert',function(){
window.alert(this);
return this;
});
var str = ' abc ';
str.trim().writeIn().alert();
//延伸
Function.prototype.method = function(name,func){
if(!this.prototype[name]){
this.prototype[name] = func;
return this;
}
};
function Person(name){
this.name = name;
}
Person.method('hide',function(){
console.log(this.name);
return this;
});
Person.method('writeIn',function(){
console.log(2);
return this;
});
Person.method('alert',function(){
console.log(3);
return this;
});
var p = new Person('andy');
p.trim().writeIn().alert();
7,用局部變量訪問集合元素
一般來說,訪問任何類型的dom,當同一個dom屬性或方法被訪問一次以上,最好使用一個局部變量 緩存改dom成員.當遍歷一個集合時,第一個要優化的是將集合引用存儲與局部變量,並在循環之外 緩存length屬性.然後,如果在循環體中多次訪問同一個集合元素,那麼使用局部變量緩存它. 下面例子,循環訪問每個元素的3個屬性.執行最慢的方法是每次都要訪問全局變量document, 優化後的代碼緩存了一個指向集合的引用,執行最快的方法是將集合的當前元素存入局部變量.
//較慢
function collGlobal(){
var coll = document.getElementsByTagName('b'),len = coll.length,name = '';
for(var i = 0;i<len;i++){
name = document.getElementsByTagName('b')[i].nodeName;
name = document.getElementsByTagName('b')[i].nodeType;
name = document.getElementsByTagName('b')[i].tagName;
}
return name;
}
//較快
function collLocal(){
var coll = document.getElementsByTagName('b'),len = coll.length,name = '';
for(var i = 0;i<len;i++){
name = coll[i].nodeName;
name = coll[i].nodeType;
name = coll[i].tagName;
}
return name;
}
//最快
function collNodesLocal(){
var coll = document.getElementsByTagName('b'),len = coll.length,name = '',el =null;
for(var i = 0;i<len;i++){
el = coll[i];
name = el.nodeName;
name = el.nodeType;
name = el.tagName;
}
return name;
}
8,推薦使用css選擇器
使用css選擇器是一個便捷的確定節點的方法,這是因爲大家已經對css狠熟悉了; 許多js庫爲此提供了API,而且,最新對瀏覽器提供了一個名爲querySelectorAll()原生瀏覽器DOM函數. 顯然這種方法比使用js和dom迭代並縮小元素列表對方法要快;
var b = document.querySelectorAll('.name b');
//如果不使用querySelectorAll,達到同樣對目的,代碼會沉長些
var b = document.getElementsByClassName('names')[0].getElementsByTagName('b');
console.log(b);
//當需要聯合查詢時,使用querySelectorAll()更加遍歷
var errs = document.querySelectorAll('div.uzais,div.nums');