你不知道的JavaScript(中卷)|附錄

宿主對(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)度。
好想每天都沒(méi)有什么事情做,但又害怕每天沒(méi)有什么事情做
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容