博客原文地址:Claiyre的個(gè)人博客
如需轉(zhuǎn)載,請(qǐng)?jiān)谖恼麻_頭注明原文地址
在JavaScript中,函數(shù)的功能十分強(qiáng)大蜓堕。它們是第一類對(duì)象,也可以作為另一個(gè)對(duì)象的方法,還可以作為參數(shù)傳入另一個(gè)函數(shù)尝盼,不僅如此吞滞,還能被一個(gè)函數(shù)返回!可以說(shuō)盾沫,在JS中裁赠,函數(shù)無(wú)處不在,無(wú)所不能赴精,堪比孫猴子呀佩捞!當(dāng)你運(yùn)用好函數(shù)時(shí),它能助你取西經(jīng)蕾哟,讓代碼變得優(yōu)雅簡(jiǎn)潔一忱,運(yùn)用不好時(shí),那就遭殃了谭确,要大鬧天宮咯~
除了函數(shù)相關(guān)的基礎(chǔ)知識(shí)外帘营,掌握一些高級(jí)函數(shù)并應(yīng)用起來(lái),不僅能讓JS代碼看起來(lái)更為精簡(jiǎn)逐哈,還可以提升性能芬迄。以下是博主總結(jié)的一些常用的、重要的高級(jí)函數(shù)昂秃,加上了一些個(gè)人見(jiàn)解禀梳,特此記錄下來(lái)。如果您是JS初學(xué)者肠骆,也不要被“高級(jí)”兩個(gè)字嚇到算途,因?yàn)槲闹写┎逯v解了一些原型、this等基礎(chǔ)知識(shí)蚀腿,相信并不難理解郊艘。如果您是JS大牛,也可以把本文用來(lái)查漏補(bǔ)缺。
正文
作用域安全的構(gòu)造函數(shù)
function Person(name,age){
this.name = name;
this.age = age;
}
var p1 = new Person("Claiyre",80);
相信您對(duì)上面的構(gòu)造函數(shù)一定不陌生纱注,但是畏浆,,如果某個(gè)粗心的程序猿調(diào)用這個(gè)構(gòu)造函數(shù)時(shí)忘記加new
了會(huì)發(fā)生什么狞贱?
var p3 = Person("Tom",30);
console.log(p3); //undefined
console.log(window.name); //Tom
由于使用了不安全的構(gòu)造函數(shù)刻获,上面的代碼意外的改變了window的name,因?yàn)?code>this對(duì)象是在運(yùn)行時(shí)綁定的瞎嬉,使用new調(diào)用構(gòu)造函數(shù)時(shí)this
是指向新創(chuàng)建的對(duì)象的蝎毡,不使用new
時(shí),this
是指向window的氧枣。
由于window的name屬性是用來(lái)識(shí)別鏈接目標(biāo)和frame的沐兵,所在這里對(duì)該屬性的偶然覆蓋可能導(dǎo)致其他錯(cuò)誤。
作用域安全的構(gòu)造函數(shù)會(huì)首先確認(rèn)this
對(duì)象是正確類型的實(shí)例便监,然后再進(jìn)行更改扎谎,如下:
function Person(name,age){
if(this instanceof Person){
this.name = name;
this.age = age;
} else {
return new Person(name,age);
}
}
這樣就避免了在全局對(duì)象上意外更改或設(shè)置屬性。
實(shí)現(xiàn)這個(gè)安全模式烧董,相當(dāng)于鎖定了調(diào)用構(gòu)造函數(shù)的環(huán)境毁靶,因此借用構(gòu)造函數(shù)繼承模式可能會(huì)出現(xiàn)問(wèn)題,解決方法是組合使用原型鏈和構(gòu)造函數(shù)模式逊移,即組合繼承预吆。
如果您是一個(gè)JS庫(kù)或框架的開發(fā)者,相信作用域安全的構(gòu)造函數(shù)一定對(duì)您非常有用胳泉。在多人協(xié)作的項(xiàng)目中拐叉,為了避免他們誤改了全局對(duì)象,也應(yīng)使用作用域安全的構(gòu)造函數(shù)扇商。
惰性載入函數(shù)
由于瀏覽器間的行為差異巷嚣,代碼中可能會(huì)有許多檢測(cè)瀏覽器行為的if語(yǔ)句。但用戶的瀏覽器若支持某一特性钳吟,便會(huì)一直支持廷粒,所以這些if語(yǔ)句,只用被執(zhí)行一次红且,即便只有一個(gè)if語(yǔ)句的代碼坝茎,也比沒(méi)有要快。
惰性載入表示函數(shù)執(zhí)行的分支僅會(huì)執(zhí)行一次暇番,有兩種實(shí)現(xiàn)惰性載入的方式嗤放,第一種就是在函數(shù)第一次被調(diào)用時(shí)再處理函數(shù),用檢測(cè)到的結(jié)果重寫原函數(shù)壁酬。
function detection(){
if(//支持某特性){
detection = function(){
//直接用支持的特性
}
} else if(//支持第二種特性){
detection = function(){
//用第二種特性
}
} else {
detection = function(){
//用其他解決方案
}
}
}
第二種實(shí)現(xiàn)惰性載入的方式是在聲明函數(shù)時(shí)就指定適當(dāng)?shù)暮瘮?shù)
var detection = (function(){
if(//支持某特性){
return function(){
//直接用支持的特性
}
} else if(//支持第二種特性){
return function(){
//用第二種特性
}
} else {
return function(){
//用其他解決方案
}
}
})();
惰性載入函數(shù)的有點(diǎn)是在只初次執(zhí)行時(shí)犧牲一點(diǎn)性能次酌,之后便不會(huì)再有多余的消耗性能恨课。
函數(shù)綁定作用域
在JS中,函數(shù)的作用域是在函數(shù)被調(diào)用時(shí)動(dòng)態(tài)綁定的岳服,也就是說(shuō)函數(shù)的this對(duì)象的指向是不定的剂公,但在一些情況下,我們需要讓某一函數(shù)的執(zhí)行作用域固定吊宋,總是指向某一對(duì)象纲辽。這時(shí)怎么辦呢?
當(dāng)當(dāng)當(dāng)~~可以用函數(shù)綁定作用域函數(shù)呀
function bind(fn,context){
return function(){
return fn.apply(context,arguments);
}
}
用法:
var person1 = {
name: "claiyre",
sayName: function(){
alert(this.name);
}
}
var sayPerson1Name = bind(person1.sayName,person1);
sayPerson1Name(); //claiyre
call
函數(shù)和apply
函數(shù)可以臨時(shí)改變函數(shù)的作用域璃搜,使用bind函數(shù)可以得到一個(gè)綁定了作用域的函數(shù)
函數(shù)柯里化(curry)
curry的概念很簡(jiǎn)單:只傳遞部分參數(shù)來(lái)調(diào)用函數(shù)拖吼,然后讓函數(shù)返回另一個(gè)函數(shù)去處理剩下的參數(shù)≌馕牵可以理解為賦予了函數(shù)“加載”的能力吊档。
許多js庫(kù)中都封裝了curry函數(shù),具體使用可以這樣唾糯。
var match = curry(function(what,str){
return str.match(what)
});
var hasNumber = match(/[0-9]+/g);
var hasSpace = match(/\s+/g)
hasNumber("123asd"); //['123']
hasNumber("hello world!"); //null
hasSpace("hello world!"); //[' '];
hasSpace("hello"); //null
console.log(match(/\s+/g,'i am Claiyre')); //直接全部傳參也可: [' ',' ']
一旦函數(shù)經(jīng)過(guò)柯里化怠硼,我們就可以先傳遞部分參數(shù)調(diào)用它,然后得到一個(gè)更具體的函數(shù)趾断。這個(gè)更具體的函數(shù)通過(guò)閉包幫我們記住了第一次傳遞的參數(shù),最后我們就可以用這個(gè)更具體的函數(shù)為所欲為啦~
一個(gè)較為簡(jiǎn)單的實(shí)現(xiàn)curry的方式:
function curry(fn){
var i = 0;
var outer = Array.prototype.slice.call(arguments,1);
var len = fn.length;
return function(){
var inner = outer.concat(Array.prototype.slice.call(arguments));
return inner.length === len?fn.apply(null,inner):function (){
var finalArgs = inner.concat(Array.prototype.slice.call(arguments));
return fn.apply(null,finalArgs);
}
}
}
debounce函數(shù)
debounce函數(shù)吩愧,又稱“去抖函數(shù)”芋酌。它的功能也很簡(jiǎn)單直接,就是防止某一函數(shù)被連續(xù)調(diào)用雁佳,從而導(dǎo)致瀏覽器卡死或崩潰脐帝。用法如下:
var myFunc = debounce(function(){
//繁重、耗性能的操作
}糖权,250);
window.addEventListener('resize',myFunc);
像窗口的resize堵腹,這類可以以較高的速率觸發(fā)的事件,非常適合用去抖函數(shù)星澳,這時(shí)也可稱作“函數(shù)節(jié)流”疚顷,避免給瀏覽器帶來(lái)過(guò)大的性能負(fù)擔(dān)。
具體的實(shí)現(xiàn)時(shí)禁偎,當(dāng)函數(shù)被調(diào)用時(shí)腿堤,不立即執(zhí)行相應(yīng)的語(yǔ)句,而是等待固定的時(shí)間w,若在w時(shí)間內(nèi)如暖,即等待還未結(jié)束時(shí)笆檀,函數(shù)又被調(diào)用了一次,則再等待w時(shí)間盒至,重復(fù)上述過(guò)程酗洒,直到最后一次被調(diào)用后的w時(shí)間內(nèi)該函數(shù)都沒(méi)有被再調(diào)用士修,則執(zhí)行相應(yīng)的代碼。
實(shí)現(xiàn)代碼如下:
function debounce(fn,wait){
var td;
return function(){
clearTimeout(td);
td= setTimeout(fn,wait);
}
}
once函數(shù)
顧名思義樱衷,once函數(shù)是僅僅會(huì)被執(zhí)行一次的函數(shù)棋嘲。具體實(shí)現(xiàn)如下:
function once(fn){
var result;
return function(){
if(fn){
result = fn(arguments);
fn = null;
}
return result;
}
}
var init = once(function(){
//初始化操作
})
在被執(zhí)行過(guò)一次后,參數(shù)fn就被賦值null了,那么在接下來(lái)被調(diào)用時(shí)箫老,便再也不會(huì)進(jìn)入到if語(yǔ)句中了封字,也就是第一次被調(diào)用后,該函數(shù)永遠(yuǎn)不會(huì)被執(zhí)行了耍鬓。
還可以對(duì)上述once函數(shù)進(jìn)行改進(jìn)阔籽,不僅可以傳入函數(shù),同時(shí)還可以給傳入的函數(shù)綁定作用域u牲蜀,同時(shí)實(shí)現(xiàn)了bind和once笆制。
function once(fn,context){
var result;
return function(){
if(fn){
result = fn.apply(context,arguments);
fn = null;
}
return result;
}
}
結(jié)語(yǔ)
通過(guò)以上的閱讀,不難發(fā)現(xiàn)很多“高級(jí)函數(shù)”的實(shí)現(xiàn)其實(shí)并不復(fù)雜涣达,數(shù)十行代碼便可搞定在辆,但重要的是能真正理解它們的原理,在實(shí)際中適時(shí)地應(yīng)用度苔,以此性能提升匆篓,讓代碼簡(jiǎn)潔,邏輯清晰