你可能不知道的一些JavaScript 奇淫巧技
? ? ? ?這里記錄一下以前學(xué)習(xí)各種書籍和文章里邊出現(xiàn)的JS的小技巧,分享給大家没咙,也供自己查閱猩谊,同時(shí)感謝那些發(fā)現(xiàn)創(chuàng)造和分享這些技巧的前輩和大牛們。
1祭刚、遍歷一個(gè)obj的屬性到數(shù)組
var a=[];
for(a[a.length] in obj);
return a;
乍一看可能比較蒙牌捷,不過仔細(xì)分析還是不難理解的。常見用法是for(var key in obj)涡驮,這里key初始也是undefined的暗甥,a[a.length]整體也是undefined,所以二者其實(shí)是等價(jià)的捉捅。在for循環(huán)中撤防,obj的屬性會依次賦值給key,同樣棒口,也依次賦值給a[a.length]寄月,這里length一直在變辜膝,就巧妙地挨個(gè)賦值給數(shù)組的每一個(gè)元素了。
2漾肮、重復(fù)字符串(如abc=>abcabc)
function repeat(target,n){
return(new Array(n+1).join(target));
}
改良版本:
function repeat(target,n){
return Array.prototype.join.call(
{length:n+1},target);//之所以要創(chuàng)建帶length屬性的對象厂抖,是因?yàn)檎{(diào)用數(shù)組原型方法時(shí),必須是一個(gè)類數(shù)組對象忱辅,而類數(shù)組對象的條件就是length為非負(fù)整數(shù)
}
不新建數(shù)組,而是用擁有l(wèi)ength屬性的對象替代谭溉,然后調(diào)用數(shù)組的join方法,性能提升很大
再改進(jìn):
var repeat=(function(){
? ? var join=Array.prototype.join,obj={};
? ? return function(target,n){
? ? ? ? obj.length=n+1;
? ? ? ? return join.call(obj,target);
? ? }
})();
利用閉包將對象和join方法緩存起來,不用每次都新建對象和尋找方法
3扮念、for循環(huán)中,當(dāng)?shù)诙?xiàng)為false時(shí)會終止循環(huán)扔亥,這里并不一定存在比較谈为,可以直接賦值旅挤,如果賦值為undefined之類的值時(shí)粘茄,轉(zhuǎn)成bool值也為假,因此也會終止秕脓,比如遍歷數(shù)組可以寫成:
for(var i=arr.length,element;element=arr[—-i];){…}
這里,第二項(xiàng)一定是arr[—-i]而非arr[i--],如果是后者的話吠架,上來就是undefined芙贫,就不會執(zhí)行循環(huán)體,或者for(vari=0,element;element=arr[i++];){…}
4傍药、NaN是JS中唯一不等于自己的值磺平,因此可以用來判斷一個(gè)變量是否真的為NaN:a!==a
5拣挪、“<”俱诸,”+”等運(yùn)算符會強(qiáng)制符號兩邊的表達(dá)式執(zhí)行valueOf然后比較,所以如果兩邊是函數(shù)或者對象赶诊,而又重寫了該對象的valueOf方法,就會自動執(zhí)行兩邊的方法出吹。如:
var a={valueOf:function(){console.log(“aaa”);}},b={valueOf:function(){console.log(“bbb”);}};
a<b;//會輸出:aaa;bbb;false;
6辙喂、JS具備自動插入分號的能力巍耗,但是自動插入分號并不是萬能的,其有三條規(guī)則:
1)只在”}”標(biāo)記之前灸蟆、一個(gè)或多個(gè)換行之后以及程序輸入的結(jié)尾被插入炒考;
2)分號只在隨后的輸入標(biāo)記不能被解析時(shí)插入霎迫;
這一點(diǎn)很重要,比如:
a=b
(f());
是不會在a=b之后自動插入分號的瓤帚,因?yàn)閍=b(f())是可以被解析的涩赢,因此像”(“,”[“,”+”,”-“,”/“開頭的時(shí)候,需要特別注意上一行可能不會自動插入怯邪。
還有一些情況花墩,盡管不會出現(xiàn)解析錯誤,JS仍然會強(qiáng)制插入分號搂捧,這就是所謂的JS語法限制產(chǎn)生式允跑。它不允許在兩個(gè)字符間出現(xiàn)換行,最危險(xiǎn)的就是return語句索烹,如
return
{};
會被強(qiáng)制插入而成為
return;
{};
類似的還有:throw語句百姓、帶有顯示標(biāo)簽的break活著continue語句况木、后置自增或自減運(yùn)算符
3)分號不會作為分隔符在for循環(huán)空語句的頭部被自動插入
因此火惊,最好的辦法是在自己的js文件的最開始防御性地插入”;”,這樣在合并js文件的時(shí)候就不會出問題了尸疆。
7寿弱、閉包按灶。理解閉包需學(xué)會三個(gè)基本事實(shí):
(1)JS允許你引用在當(dāng)前函數(shù)意外定義的變量
(2)即使外部函數(shù)已經(jīng)返回,當(dāng)前函數(shù)仍然可以引用在外部函數(shù)所定義的變量地沮。這是因?yàn)镴S的函數(shù)值包含里比調(diào)用它們時(shí)執(zhí)行所需要的代碼更多的信息
(3)閉包可以更新外部變量的值。這是因?yàn)殚]包存儲的是外部變量的引用而非值副本危融。如:
function box(){
? ? var ? val=undefined;
? ? return{
? ? ? ? set:function(x){val=x;},
? ? ? ? get:function(){return val;}
? ? };
}
var b=box();
b.get();//“undefined”
b.set(5);
b.get();//5
這一點(diǎn)很重要吉殃,比如在函數(shù)的for循環(huán)體內(nèi)返回閉包或者有閉包取for循環(huán)的計(jì)數(shù)器值,那么這個(gè)閉包取到的永遠(yuǎn)是for循環(huán)結(jié)束時(shí)i的最終值瓦灶,因?yàn)殚]包存儲的是它的引用而非當(dāng)時(shí)的值副本。
8碉怔、JS沒有塊級作用域桨踪,因此通常情況下函數(shù)內(nèi)部的所有變量都是綁定到函數(shù)作用域的,也就是說相當(dāng)于都在函數(shù)一開始就聲明了的纳账,一個(gè)例外就是try/catch中的變量是塊級的,只屬于try/catch塊啤呼。
9、眾所周知惕蹄,在函數(shù)內(nèi)部聲明函數(shù)是可以的卖陵,但是在在函數(shù)內(nèi)的局部塊里聲明,可能會出現(xiàn)問題:
function f(){return“global”;}
function test(x){
? ? function f(){return“l(fā)ocal”}
? ? var ? ?result=[];
? ? if(x){
? ? ? ? result.push(f());
? ? }
? ? result.push(f());
? ? return result;
}
test(true);//[“l(fā)ocal”,”local”]
test(false);//[“l(fā)ocal”]
將函數(shù)聲明到if塊中:
function f(){return“global”;}
? ? function test(x){
? ? var ?result=[];
? ? if(x){
? ? ? ? functionf (){return“l(fā)ocal”}
? ? ? ? result.push(f());
? ? }
? ? result.push(f());
? ? return result;
}
test(true);//?
test(false);//?
結(jié)果會如何呢?理論上講餐曹,JS沒有塊級作用域俱两,因此f()的作用域是整個(gè)test函數(shù)锋华,因此合理猜測應(yīng)該是與上一次輸出相同,全部為”local”磺樱,可是并不是所有的JS執(zhí)行環(huán)境都如此行事,有的會根據(jù)是否執(zhí)行包含f的代碼塊來有條件地綁定函數(shù)f(綁定即意味著將該變量綁定到其最近的作用域块差,而賦值是發(fā)生在代碼實(shí)際執(zhí)行到賦值那一步的時(shí)候進(jìn)行的)状蜗。
因此最好的辦法是如果要聲明嵌套函數(shù),都在其富函數(shù)的最外層聲明缸血,要么就不要聲明函數(shù),而是使用var聲明和函數(shù)表達(dá)式來實(shí)現(xiàn):
function f(){return “global”;}
function test(x){
? ? varresult=[];
? ? if(x){
? ? ? ? var g=function(){return “l(fā)ocal”}
? ? ? ? result.push(g());
? ? }
? ? result.push(f());
? ? return result;
}
10族扰、用js創(chuàng)建字典的時(shí)候怒竿,如果是利用對象的方式(因?yàn)镴S對象的核心是一個(gè)字符串屬性名稱和屬性值的映射表)爷辱,會遇到一個(gè)問題就是原型污染双饥,因?yàn)楂@取字典屬性值的時(shí)候用hasOwnProperty還好,如果用for in遍歷的話阀趴,不僅會遍歷對象本身棚菊,包括它的原型,因此如果在其他地方污染了Object的原型码邻,那么for in就會產(chǎn)生非預(yù)期的結(jié)果,這時(shí)可能會用hasOwnProperty來先檢測該對象本身是否含有屬性來避免原型污染浪谴,然而更極端的情況是連hasOwnProperty這個(gè)原型方法都有可能被污染。避免原型污染的方法是在創(chuàng)建字典對象的時(shí)候用Object.create(null)來創(chuàng)建一個(gè)完全空對象胁艰,這個(gè)對象沒有原型腾么,這個(gè)方法是ES5的,在沒有這個(gè)方法可用的時(shí)候,最好是創(chuàng)建字典類殴泰,然后在字典類里用數(shù)組來存儲有序集合捞魁,自己維護(hù)這個(gè)集合。
11健霹、JS中的類數(shù)組對象可以享用數(shù)組的大部分原型方法
如map等宣吱,類數(shù)組對象是指滿足兩個(gè)條件的對象:一是具備合理范圍值內(nèi)的length屬性,二是length屬性大于該對象的最大索引疤坝,索引是一個(gè)合理范圍的證書跑揉,它的字符串表示的是對象的一個(gè)key;但是數(shù)組的一個(gè)原型方法contact是不能被類數(shù)組對象調(diào)用的望侈,因此需要先用[].slice.call把類數(shù)組對象轉(zhuǎn)換為真正的數(shù)組比如[].slice.call(arguments)。
12捐韩、并不是所有時(shí)候都需要繼承,繼承也不是完美的寨蹋,有時(shí)候會創(chuàng)造比他能解決的更多的問題,特別是當(dāng)層次關(guān)系沒那么明顯的時(shí)候
這時(shí)候應(yīng)該多用結(jié)構(gòu)類型(又叫鴨子類型召娜,如果它看起來像鴨子秸讹、游泳像鴨子并且叫聲像鴨子璃诀,那么它就是鴨子),用結(jié)構(gòu)類型設(shè)計(jì)靈活的對象接口的時(shí)候凿将,不需要創(chuàng)建類工廠來返回類的實(shí)例,而是直接返回對象犀变,對象具備預(yù)期的方法和屬性,比如:
SomeObj.someWidget=function(opts){
? ? return{
? ? ? ? a:blabla,
? ? ? ? b:function(){...},
? ? ? ? c:blabla
? ? }
}