宿主對(duì)象
JavaScript中有關(guān)變量的規(guī)則定義得十分清楚,但也不乏一些例外情況,比如自動(dòng)定義的變量,以及由宿主環(huán)境(瀏覽器等)創(chuàng)建并提供給JavaScript引擎的變量——所謂的“宿主對(duì)象”(包括內(nèi)建對(duì)象和函數(shù))。
var a = document.createElement( "div" );
typeof a; // "object"--正如所料
Object.prototype.toString.call( a ); // "[object HTMLDivElement]"
a.tagName; // "DIV"
上例中,a不僅僅是一個(gè)object,還是一個(gè)特殊的宿主對(duì)象,因?yàn)樗且粋€(gè)DOM元素。其內(nèi)部的[[Class]]值(為“HTMLDivElement”)來(lái)自預(yù)定義的屬性(通常也是不可更改的)。
其他需要注意的宿主對(duì)象的行為差異有:
- 無(wú)法訪問(wèn)正常的object內(nèi)建方法,如toString();
- 無(wú)法寫(xiě)覆蓋;
- 包含一些預(yù)定義的只讀屬性;
- 包含無(wú)法將this重載為其他對(duì)象的方法;
- 其他......
在針對(duì)運(yùn)行環(huán)境進(jìn)行編碼時(shí),宿主對(duì)象扮演著一個(gè)十分關(guān)鍵的角色,但要特別注意其行為特性,因?yàn)樗鼈兂3S袆e于普通的JavaScript object。
在我們經(jīng)常打交道的宿主對(duì)象中,console及其各種方法(log(..)、error(..)等)是比較值得一提的。console對(duì)象由宿主環(huán)境提供,以便從代碼中輸出各種值。
console在瀏覽器中是輸出到開(kāi)發(fā)工具控制臺(tái),而在Node.js和其他服務(wù)器JavaScript環(huán)境中,則是指向JavaScript環(huán)境系統(tǒng)進(jìn)程的標(biāo)準(zhǔn)輸出(stdout)和標(biāo)準(zhǔn)錯(cuò)誤輸出(stderr)。
全局DOM變量
你可能已經(jīng)知道,聲明一個(gè)全局變量(使用var或者不使用)的結(jié)果并不僅僅是創(chuàng)建一個(gè)全局變量,而且還會(huì)在global對(duì)象(在瀏覽器中為window)中創(chuàng)建一個(gè)同名屬性。
還有一個(gè)不太為人所知的事實(shí)是:由于瀏覽器演進(jìn)的歷史遺留問(wèn)題,在創(chuàng)建帶有id屬性的DOM元素時(shí)也會(huì)創(chuàng)建同名的全局變量:
<div id="foo"></div>
if (typeof foo == "undefined") {
foo = 42; // 永遠(yuǎn)也不會(huì)運(yùn)行
}
console.log(foo); // HTML元素
你可能認(rèn)為只有JavaScript代碼才能創(chuàng)建全局變量,并且習(xí)慣使用typeof或..in window來(lái)檢測(cè)全局變量。但是如上例所示,HTML頁(yè)面中的內(nèi)容也會(huì)產(chǎn)生全局變量,并且稍不注意就很容易讓全局變量檢查錯(cuò)誤百出。
這也是盡量不要使用全局變量的一個(gè)原因。如果確實(shí)要用,也要確保變量名的唯一性,從而避免與其他地方的變量產(chǎn)生沖突,包括HTML和其他第三方代碼。
原生原型
一個(gè)廣為人知的JavaScript的最佳實(shí)踐是:不要擴(kuò)展原生原型。
如果向Array.prototype中加入新的方法和屬性,假設(shè)它們確實(shí)有用,設(shè)計(jì)和命名都很得當(dāng),那它最后很有可能會(huì)被加入到JavaScript規(guī)范當(dāng)中。這樣一來(lái)你所做的擴(kuò)展就會(huì)與之沖突。
所以,首先不要擴(kuò)展原生方法,除非你確信代碼在運(yùn)行環(huán)境中不會(huì)有沖突。如果對(duì)此你并非100%確定,那么進(jìn)行擴(kuò)展是非常危險(xiǎn)的。這需要你自己仔細(xì)權(quán)衡利弊。
其次,在擴(kuò)展原生方法時(shí)需要加入判斷條件(因?yàn)槟憧赡軣o(wú)意中覆蓋了原來(lái)的方法)。對(duì)于前面的例子,下面的處理方式要更好一些:
if (!Array.prototype.push) {
// Netscape 4沒(méi)有Array.push
Array.prototype.push = function (item) {
this[this.length - 1] = item;
};
}
shim/polyfill
polyfill(或者shim)能有效地為不符合最新規(guī)范的老版本瀏覽器填補(bǔ)缺失的功能,讓你能夠通過(guò)可靠的代碼來(lái)支持所有你想要支持的運(yùn)行環(huán)境。
ES5-Shim(https://github.com/es-shims/es5-shim) 是一個(gè)完整的shim/polyfill
集合,能夠?yàn)槟愕捻?xiàng)目提供ES5 基本規(guī)范支持。同樣,ES6-Shim(https://
github.com/es-shims/es6-shim)提供了對(duì)ES6 基本規(guī)范的支持。雖然我們可
以通過(guò)shim/polyfill 來(lái)填補(bǔ)新的API,但是無(wú)法填補(bǔ)新的語(yǔ)法??梢允褂?br> Traceur(https://github.com/google/traceur-compiler/wiki/GettingStarted) 這樣
的工具來(lái)實(shí)現(xiàn)新舊語(yǔ)法之間的轉(zhuǎn)換。
對(duì)于將來(lái)可能成為標(biāo)準(zhǔn)的功能,按照大部分人贊同的方式來(lái)預(yù)先實(shí)現(xiàn)能和將來(lái)的標(biāo)準(zhǔn)兼容的polyfill,我們成為prollyfill(probably fill)。
<script>
絕大部分網(wǎng)站/Web應(yīng)用程序的代碼都存放在多個(gè)文件中,通常可以在網(wǎng)頁(yè)中使用<scriopt src=..></script>來(lái)加載這些文件,或者使用<script> .. </script>來(lái)包含內(nèi)斂代碼。
這些文件和內(nèi)聯(lián)代碼是相互獨(dú)立的JavaScript程序還是一個(gè)整體呢?
答案是它們的運(yùn)行方式更像是相互獨(dú)立的JavaScript程序,但是并非總是如此。
它們共享global對(duì)象(在瀏覽器中在是window),也就是說(shuō)這些文件中的代碼在共享的命名空間中運(yùn)行,并相互交互。
如果某個(gè)script中定義了函數(shù)foo(),后面的script代碼就可以訪問(wèn)并調(diào)用foo(),就像foo()在其內(nèi)部被聲明過(guò)一樣。
但是全局變量作用域的提升機(jī)制在這些便捷中不適用,因此無(wú)論是<script> .. </script>還是<script src=..></script>,下面的代碼都無(wú)法運(yùn)行(因?yàn)閒oo()還未被聲明):
<script>foo();</script>
<script>
function foo() { .. }
</script>
但是下面的兩端代碼則沒(méi)問(wèn)題:
<script>
foo();
function foo() { .. }
</script>
和:
<script>
function foo() { .. }
</script>
<script>foo();</script>
如果script中的代碼(無(wú)論是內(nèi)聯(lián)代碼還是外部代碼)發(fā)生錯(cuò)誤,它會(huì)像獨(dú)立的JavaScript程序那樣停止,但是后續(xù)的script中的代碼(仍然共享global)依然會(huì)接著運(yùn)行,不會(huì)受影響。
你可以使用代碼來(lái)動(dòng)態(tài)創(chuàng)建script,將其加入到頁(yè)面的DOM中,效果是一樣的:
var greeting = "Hello World";
var el = document.createElement("script");
el.text = "function foo(){ alert( greeting );\
} setTimeout( foo, 1000 );";
document.body.appendChild(el);
如果將el.src的值設(shè)置為一個(gè)文件URL,就可以通過(guò)
<script src=""></script>動(dòng)態(tài)加載外部文件。
內(nèi)聯(lián)代碼和外部文件中的代碼之間有一個(gè)區(qū)別,即在內(nèi)聯(lián)代碼中不可以出現(xiàn)</script>字符串,一旦出現(xiàn)即被視為代碼結(jié)束。因此對(duì)于下面這樣的代碼需要非常小心:
<script>
var code = "<script>alert( ‘Hello World’ )</script>";
</script>
上述代碼看似沒(méi)什么問(wèn)題,但是字符串常量中的</script>將會(huì)被當(dāng)作結(jié)束標(biāo)簽來(lái)處理,因此會(huì)導(dǎo)致錯(cuò)誤。常用的變通方法是:
"</sc" + "ript>";
另外需要注意的一點(diǎn)是,我們是根據(jù)代碼文件的字符集屬性(UTF-8、ISO-8859-8等)來(lái)解析外部文件中的代碼(或者默認(rèn)字符集),而內(nèi)聯(lián)代碼則使用其所在頁(yè)面文件的字符集(或者默認(rèn)字符集)。
現(xiàn)實(shí)中的限制
JavaScript規(guī)范對(duì)于函數(shù)中參數(shù)的個(gè)數(shù),以及字符串常量的長(zhǎng)度等并沒(méi)有限制;但是由于JavaScript引擎實(shí)現(xiàn)各異,規(guī)范在某些地方有一些限制。
function addAll() {
var sum = 0;
for (var i = 0; i < arguments.length; i++) {
sum += arguments[i];
}
return sum;
}
var nums = [];
for (var i = 1; i < 100000; i++) {
nums.push(i);
}
addAll(2, 4, 6); // 12
addAll.apply(null, nums); // 應(yīng)該是: 499950000
在一些JavaScript引擎中你會(huì)得到正確答案499950000,而另外一些引擎(如Safari 6.x)中則會(huì)產(chǎn)生錯(cuò)誤“RangeError: Maximum call stack size exceeded”。
下面列出一些已知的限制:
- 字符串常量中允許的最大字符數(shù)(并非只是針對(duì)字符串值);
- 可以作為參數(shù)傳遞到函數(shù)中的數(shù)據(jù)大?。ㄒ卜Q為棧大小,以字節(jié)為單位);
- 函數(shù)聲明中的參數(shù)個(gè)數(shù);
- 未經(jīng)優(yōu)化的調(diào)用棧(例如遞歸)的最大層數(shù),即函數(shù)調(diào)用鏈的最大長(zhǎng)度;
- JavaScript程序以阻塞方式在瀏覽器中運(yùn)行的最長(zhǎng)時(shí)間(秒);
- 變量名的最大長(zhǎng)度。
