如何編寫(xiě)高質(zhì)量JS代碼
想寫(xiě)出高效的javascript類(lèi)庫(kù)卻無(wú)從下手;
嘗試閱讀別人的類(lèi)庫(kù),卻理解得似懂給懂;
打算好好鉆研js高級(jí)函數(shù),但權(quán)威書(shū)上的內(nèi)容太零散,
即使記住“用法”,但到要“用”的時(shí)候卻沒(méi)有想“法”。
也許你和我一樣,好像有一顧無(wú)形的力量約束著我們的計(jì)劃,讓我們一再認(rèn)為知識(shí)面的局限性,致使我們?cè)靥げ?,難以向前跨越。
這段時(shí)間,各種作業(yè)、課程設(shè)計(jì)、實(shí)驗(yàn)報(bào)告,壓力倍增。難得擠出一點(diǎn)點(diǎn)時(shí)間,絕不睡懶覺(jué),整理總結(jié)往日所看的書(shū),只為了可以離寫(xiě)自己的類(lèi)庫(kù)近一點(diǎn)。
本文參考自《javascript語(yǔ)言精粹》和《Effective JavaScript》。例子都被調(diào)試過(guò),理解過(guò)后,我想把一些“深?yuàn)W”的道理說(shuō)得淺顯一點(diǎn)點(diǎn)。
1.變量作用域
作用域?qū)τ诔绦騿T來(lái)說(shuō)就像氧氣。它無(wú)處不在,甚至,你往往不會(huì)去想他。但當(dāng)它被污染時(shí)(例如使用全局對(duì)象),你會(huì)感覺(jué)到窒息(例如應(yīng)用響應(yīng)變慢)。javascript核心作用域規(guī)則很簡(jiǎn)單,被精心設(shè)計(jì),且很強(qiáng)大。有效地使用javascript需要掌握變量作用域的一些基本概念,并了解一些可能導(dǎo)致難以捉摸的、令人討厭的問(wèn)題的極端情況。
1.1盡量少用全局變量
javascript很容易在全局命名空間中創(chuàng)建變量。創(chuàng)建全局變量毫不費(fèi)力,因?yàn)樗恍枰魏涡问降穆暶鳎夷鼙徽麄€(gè)程序的所有代碼自動(dòng)地訪問(wèn)。
對(duì)于我們這些初學(xué)者,遇到某些需求(例如,傳輸?shù)臄?shù)據(jù)被記錄下來(lái)、等待某時(shí)機(jī)某函數(shù)調(diào)用時(shí)使用;或者是某函數(shù)被經(jīng)常使用)時(shí),好不猶豫想到全局函數(shù),甚至大一學(xué)到的C語(yǔ)言面向過(guò)程思想太根深蒂固,系統(tǒng)整整齊齊地都是滿滿函數(shù)。定義全局變量會(huì)污染共享的公共命名空間,并可能導(dǎo)致意外的命名沖突。全局變量也不利于模塊化,因?yàn)樗鼤?huì)導(dǎo)致程序中獨(dú)立組件間的不必要耦合。嚴(yán)重地說(shuō),過(guò)多的全局(包括樣式表,直接定義div或者a的樣式),整合到多人開(kāi)發(fā)過(guò)稱(chēng)將會(huì)成為災(zāi)難性錯(cuò)誤。這就是為什么jQuery的所有代碼都被包裹在一個(gè)立即執(zhí)行的匿名表達(dá)式——自調(diào)用匿名函數(shù)。當(dāng)瀏覽器加載完jQuery文件后,自調(diào)用匿名函數(shù)立即開(kāi)始執(zhí)行,初始化jQuery的各個(gè)模塊,避免破壞和污染全局變量以至于影響到其他代碼。
(function(window,undefined){
var jQuery = ...
//...
window.jQuery = window.$ = jQuery;
})(window);
另外,你或許會(huì)認(rèn)為,“先怎么怎么寫(xiě),日后再整理”比較方便,但優(yōu)秀的程序員會(huì)不斷地留意程序的結(jié)構(gòu)、持續(xù)地歸類(lèi)相關(guān)的功能以及分離不相關(guān)的組件,并這些行為作為編程過(guò)稱(chēng)中的一部分。
由于全局命名空間是javascript程序中獨(dú)立的組件經(jīng)行交互的唯一途徑,因此,利用全局命名控件的情況是不可避免的。組件或程序庫(kù)不得不定義一些全局變量。以便程序中的其他部分使用。否則最好使用局部變量。
this.foo ;//undefined
foo = " global foo";
this.foo ;//"global foo"
var foo = "global foo";
this.foo = "changed";
foo ;//changed
javascript的全局命名空間也被暴露在程序全局作用域中可以訪問(wèn)的全局對(duì)象,該對(duì)象作為this關(guān)鍵字的初始值。在web瀏覽器中,全局對(duì)象被綁定在全局window變量。這就意味你創(chuàng)建全局變量有兩種方法:在全局作用域內(nèi)使用var聲明他,或者將其加入到全局對(duì)象中。使用var聲明的好處是能清晰地表達(dá)全局變量在程序范圍中的影響。
鑒于引用為綁定的全局變量會(huì)導(dǎo)致運(yùn)行時(shí)錯(cuò)誤,因此,保存作用域清晰和簡(jiǎn)潔會(huì)使代碼的使用者更容易理解程序聲明了那些全局變量。
由于全局對(duì)象提供了全局環(huán)境的動(dòng)態(tài)反應(yīng)機(jī)制,所以可以使用它查詢一個(gè)運(yùn)行環(huán)境,檢測(cè)在這個(gè)平臺(tái)下哪些特性可用。
eg.ES5引入了一個(gè)全局的JSON對(duì)象來(lái)讀寫(xiě)JSON格式的數(shù)據(jù)。
if(!this.JSON){
this.JSON = {
parse : ..,
stringify : ...
}
}
如果你提供了JSON的實(shí)現(xiàn),你當(dāng)然可以簡(jiǎn)單無(wú)條件地使用自己的實(shí)現(xiàn)。但是由宿主環(huán)境提供的內(nèi)置實(shí)現(xiàn)幾乎更適合的,因?yàn)樗鼈兪怯肅語(yǔ)言寫(xiě)進(jìn)瀏覽器的。因?yàn)樗鼈儼凑找欢ǖ臉?biāo)準(zhǔn)對(duì)正確性和一致性進(jìn)行了嚴(yán)格檢查,并且普遍來(lái)說(shuō)比第三方實(shí)現(xiàn)提供更好的性能。
當(dāng)初數(shù)據(jù)結(jié)構(gòu)課程設(shè)計(jì)模擬串的基本操作,要求不能使用語(yǔ)言本身提供的方法。javascript對(duì)數(shù)組的基本操作實(shí)現(xiàn)得很好,如果只是出于一般的學(xué)習(xí)需要,模擬語(yǔ)言本身提供的方法的想法很好,但是如果真正投入開(kāi)發(fā),無(wú)需考慮第一時(shí)間選擇使用javascript內(nèi)置方法。
1.2避免使用with
with語(yǔ)句提供任何“便利“,讓你的應(yīng)用變得不可靠和低效率。我們需要對(duì)單一對(duì)象依次調(diào)用一系列方法。使用with語(yǔ)句可以很方便地避免對(duì)對(duì)象的重復(fù)引用:
function status(info){
var widget = new Widget();
with(widget){
setBackground("blue");
setForeground("white");
setText("Status : "+info);
show();
}
}
使用with語(yǔ)句從模塊對(duì)象中”導(dǎo)入“(import)變量也是很有誘惑力的。
function f(x,y){
with(Math){
return min(round(x),sqrt(y));//抽象引用
}
}
事實(shí)上,javascript對(duì)待所有的變量都是相同的。javascript從最內(nèi)層的作用域開(kāi)始向外查找變量。with語(yǔ)言對(duì)待一個(gè)對(duì)象猶如該對(duì)象代表一個(gè)變量作用域,因此,在with代碼塊的內(nèi)部,變量查找從搜索給定的變量名的屬性開(kāi)始。如果在這個(gè)對(duì)象中沒(méi)有找到該屬性,則繼續(xù)在外部作用域中搜索。with塊中的每個(gè)外部變量的引用都隱式地假設(shè)在with對(duì)象(以及它的任何原型對(duì)象)中沒(méi)有同名的屬性。而在程序的其他地方創(chuàng)建或修改with對(duì)象或其原型對(duì)象不一定會(huì)遵循這樣的假設(shè)。javascript引擎當(dāng)然不會(huì)讀取局部代碼來(lái)獲取你使用了那些局部變量。javascript作用域可被表示為高效的內(nèi)部數(shù)據(jù)結(jié)構(gòu),變量查找會(huì)非??焖?。但是由于with代碼塊需要搜索對(duì)象的原型鏈來(lái)查找with代碼里的所有變量,因此,其運(yùn)行速度遠(yuǎn)遠(yuǎn)低于一般的代碼塊。
替代with語(yǔ)言,簡(jiǎn)單的做法,是將對(duì)象綁定在一個(gè)簡(jiǎn)短的變量名上。
function status(info){
var w = new Widget();
w.setBackground("blue");
w.setForeground("white");
w.setText("Status : "+info);
w.show();
}
其他情況下,最好的方法是將局部變量顯式地綁定到相關(guān)的屬性上。
function f(x,y){
var min = Math.min,
round = Math.round,
sqrt = Math.sqrt;
return min(round(x),sqrt(y));
}
1.3熟練掌握閉包
理解閉包有單個(gè)概念:
a)javascript允許你引用在當(dāng)前函數(shù)以外定義的變量。
function makeSandwich(){
var magicIngredient = "peanut butter";
function make(filling){
return magicIngredient + " and " + filling;
}
return make("jelly");
}
makeSandwich();// "peanut butter and jelly"
b)即使外部函數(shù)已經(jīng)返回,當(dāng)前函數(shù)仍然可以引用在外部函數(shù)所定義的變量
function makeSandwich(){
var magicIngredient = "peanut butter";
function make(filling){
return magicIngredient + " and " + filling;
}
return make;
}
var f = sandwichMaker();
f("jelly"); // "peanut butter and jelly"
f("bananas"); // "peanut butter and bananas"
f("mallows"); // "peanut butter and mallows"
javascriptd的函數(shù)值包含了比調(diào)用它們時(shí)所執(zhí)行所需要的代碼還要多的信息。而且,javascript函數(shù)值還在內(nèi)部存儲(chǔ)它們可能會(huì)引用的定義在其封閉作用域的變量。那些在其所涵蓋的作用域內(nèi)跟蹤變量的函數(shù)被稱(chēng)為閉包。
make函數(shù)就是一個(gè)閉包,其代碼引用了兩個(gè)外部變量:magicIngredient和filling。每當(dāng)make函數(shù)被調(diào)用時(shí),其代碼都能引用這兩個(gè)變量,因?yàn)殚]包存儲(chǔ)了這兩個(gè)變量。
函數(shù)可以引用在其作用域內(nèi)的任何變量,包括參數(shù)和外部函數(shù)變量。我們可以利用這一點(diǎn)來(lái)編寫(xiě)更加通用的sandwichMaker函數(shù)。
function makeSandwich(magicIngredient){
function make(filling){
return magicIngredient + " and " + filling;
}
return make;
}
var f = sandwichMaker(”ham“);
f("cheese"); // "ham and cheese"
f("mustard"); // "ham and mustard"
閉包是javascript最優(yōu)雅、最有表現(xiàn)力的特性之一,也是許多習(xí)慣用法的核心。
c)閉包可以更新外部變量的值。事實(shí)上,閉包存儲(chǔ)的是外部變量的引用,而不是它們的值的副本。因此,對(duì)于任何具有訪問(wèn)這些外部變量的閉包,都可以進(jìn)行更新。
function box(){
var val = undefined;
return {
set : function(newval) {val = newval;},
get : function (){return val;},
type : function(){return typeof val;}
};
}
var b = box();
b.type(); //undefined
b.set(98.6);
b.get();//98.6
b.type();//number
該例子產(chǎn)生一個(gè)包含三個(gè)閉包的對(duì)象。這三個(gè)閉包是set,type和get屬性,它們都共享訪問(wèn)val變量,set閉包更新val的值。隨后調(diào)用get和type查看更新的結(jié)果。
1.4理解變量聲明提升
javascript支持此法作用域(對(duì)變量foo的引用會(huì)被綁定到聲明foo變量最近的作用域中),但不支持塊級(jí)作用域(變量定義的作用域并不是離其最近的封閉語(yǔ)句或代碼塊)。
不明白這個(gè)特性將會(huì)導(dǎo)致一些微妙的bug:
function isWinner(player,others){
var highest = 0;
for(var i = 0,n = others.length ;i<n;i++){
var player = others[i];
if(player.score > highest){
highest = player.score;
}
}
return player.score > highest;
}
1.5 當(dāng)心命名函數(shù)表達(dá)式笨拙的作用域
function double(x){ return x*2; }
var f = function(x){ return x*2; }
同一段函數(shù)代碼也可以作為一個(gè)表達(dá)式,卻具有截然不同的含義。匿名函數(shù)和命名函數(shù)表達(dá)式的官方區(qū)別在于后者會(huì)綁定到與其函數(shù)名相同的變量上,該變量作為該函數(shù)的一個(gè)局部變量。這可以用來(lái)寫(xiě)遞歸函數(shù)表達(dá)式。
var f = function find(tree,key){
//....
return find(tree.left , key) ||
find(tree.right,key);
}
值得注意的是,變量find的作用域只在其自身函數(shù)中,不像函數(shù)聲明,命名函數(shù)表達(dá)式不能通過(guò)其內(nèi)部的函數(shù)名在外部被引用。
find(myTree,"foo");//error : find is not defined;
var constructor = function(){ return null; }
var f= function(){
return constructor();
};
f();//{}(in ES3 environments)
該程序看起來(lái)會(huì)產(chǎn)生null,但其實(shí)會(huì)產(chǎn)生一個(gè)新的對(duì)象。
因?yàn)槊瘮?shù)變量作用域內(nèi)繼承了Object.prototype.constructor(即Oject的構(gòu)造函數(shù)),就像with語(yǔ)句一樣,這個(gè)作用域會(huì)因Object.prototype的動(dòng)態(tài)改變而受到影響。在系統(tǒng)中避免對(duì)象污染函數(shù)表達(dá)式作用域的辦法是避免任何時(shí)候在Object.prototype中添加屬性,以避免使用任何與標(biāo)準(zhǔn)Object.prototype屬性同名的局部變量。
在流行的javascript引擎中另外一個(gè)缺點(diǎn)是對(duì)命名函數(shù)表達(dá)式的聲明進(jìn)行提升。
var f = function g(){return 17;}
g(); //17 (in nonconformat environment)
一些javascript環(huán)境甚至把f和g這兩個(gè)函數(shù)作為不同的對(duì)象,從而導(dǎo)致不必要的內(nèi)存分配。
1.6 當(dāng)心局部塊函數(shù)聲明笨拙的作用域
function f() {return "global" ; }
function test(x){
function f(){return "local";}
var result = [];
if(x){
result.push(f());
}
result.push(f());
result result;
}
test(true); //["local","local"]
test(false); //["local"]
function f() {return "global" ; }
function test(x){
var result = [];
if(x){
function f(){return "local";}
result.push(f());
}
result.push(f());
result result;
}
test(true); //["local","local"]
test(false); //["local"]
javascript沒(méi)有塊級(jí)作用域,所以內(nèi)部函數(shù)f的作用域應(yīng)該是整個(gè)test函數(shù)。一些javascript環(huán)境確實(shí)如此,但并不是所有javascript環(huán)境都這樣,javascript實(shí)現(xiàn)在嚴(yán)格模式下將這類(lèi)函數(shù)報(bào)告為錯(cuò)誤(具有局部塊函數(shù)聲明的處于嚴(yán)格模式下的程序?qū)?bào)告成一個(gè)語(yǔ)法錯(cuò)誤),有助于檢測(cè)不可移植代碼,為未來(lái)的標(biāo)準(zhǔn)版本在給局部塊函數(shù)聲明給更明智和可以的語(yǔ)義。針對(duì)這種情況,可以考慮在test函數(shù)內(nèi)聲明一局部變量指向全局函數(shù)f。
相關(guān)文章
ECMAScript5(ES5)中bind方法使用小結(jié)
這篇文章主要介紹了ECMAScript5(ES5)中bind方法使用小結(jié),bind和call以及apply一樣,都是可以改變上下文的this指向的,需要的朋友可以參考下2015-05-05移動(dòng)端自適應(yīng)flexible.js的使用方法(不用三大框架,僅寫(xiě)一個(gè)單html頁(yè)面使用)推薦
這篇文章主要介紹了移動(dòng)端自適應(yīng)flexible.js使用方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04JavaScript的對(duì)象和包裝類(lèi)你了解多少
這篇文章主要為大家詳細(xì)介紹了JavaScript的對(duì)象和包裝類(lèi),使用數(shù)據(jù)庫(kù),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02JavaScript入門(mén)之對(duì)象與JSON詳解
JSON是JavaScript中對(duì)象的字面量,是對(duì)象的表示方法,通過(guò)使用JSON,可以減少中間變量,使代碼的結(jié)構(gòu)更加清晰,也更加直觀。使用JSON,可以動(dòng)態(tài)的構(gòu)建對(duì)象,而不必通過(guò)類(lèi)來(lái)進(jìn)行實(shí)例化,大大的提高了編碼的效率2011-10-10JavaScript字符串對(duì)象的concat方法實(shí)例(用于連接兩個(gè)或多個(gè)字符串)
這篇文章主要介紹了JavaScript字符串對(duì)象的concat方法實(shí)例,這個(gè)方法用于連接兩個(gè)或多個(gè)字符串,平時(shí)用+號(hào)比較多,所以這個(gè)方法可能不太常用,需要的朋友可以參考下2014-10-10js實(shí)現(xiàn)日歷可獲得指定日期周數(shù)及星期幾示例分享(js獲取星期幾)
編寫(xiě)一個(gè)簡(jiǎn)易日歷。在文本框中輸入要查找的日期,程序可以計(jì)算出這一天處在該年份的第幾周,并且能判斷出這一天到底是星期幾,需要的朋友可以參考下2014-03-03javascript七大數(shù)據(jù)類(lèi)型詳解
這篇文章主要為大家介紹了 javascript七大數(shù)據(jù)類(lèi)型,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2021-12-12javascript學(xué)習(xí)筆記(十二) RegExp類(lèi)型介紹
javascript學(xué)習(xí)筆記之RegExp類(lèi)型介紹,學(xué)習(xí)js的朋友可以參考下2012-06-06