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

宿主對象
JavaScript中有關(guān)變量的規(guī)則定義得十分清楚,但也不乏一些例外情況,比如自動定義的變量,以及由宿主環(huán)境(瀏覽器等)創(chuàng)建并提供給JavaScript引擎的變量——所謂的“宿主對象”(包括內(nèi)建對象和函數(shù))。

var a = document.createElement( "div" );
typeof a; // "object"--正如所料
Object.prototype.toString.call( a ); // "[object HTMLDivElement]"
a.tagName; // "DIV"

上例中,a不僅僅是一個object,還是一個特殊的宿主對象,因?yàn)樗且粋€DOM元素。其內(nèi)部的[[Class]]值(為“HTMLDivElement”)來自預(yù)定義的屬性(通常也是不可更改的)。
其他需要注意的宿主對象的行為差異有:

  • 無法訪問正常的object內(nèi)建方法,如toString();
  • 無法寫覆蓋;
  • 包含一些預(yù)定義的只讀屬性;
  • 包含無法將this重載為其他對象的方法;
  • 其他......

在針對運(yùn)行環(huán)境進(jìn)行編碼時,宿主對象扮演著一個十分關(guān)鍵的角色,但要特別注意其行為特性,因?yàn)樗鼈兂3S袆e于普通的JavaScript object。
在我們經(jīng)常打交道的宿主對象中,console及其各種方法(log(..)、error(..)等)是比較值得一提的。console對象由宿主環(huán)境提供,以便從代碼中輸出各種值。
console在瀏覽器中是輸出到開發(fā)工具控制臺,而在Node.js和其他服務(wù)器JavaScript環(huán)境中,則是指向JavaScript環(huán)境系統(tǒng)進(jìn)程的標(biāo)準(zhǔn)輸出(stdout)和標(biāo)準(zhǔn)錯誤輸出(stderr)。

全局DOM變量
你可能已經(jīng)知道,聲明一個全局變量(使用var或者不使用)的結(jié)果并不僅僅是創(chuàng)建一個全局變量,而且還會在global對象(在瀏覽器中為window)中創(chuàng)建一個同名屬性。
還有一個不太為人所知的事實(shí)是:由于瀏覽器演進(jìn)的歷史遺留問題,在創(chuàng)建帶有id屬性的DOM元素時也會創(chuàng)建同名的全局變量:

<div id="foo"></div>
if (typeof foo == "undefined") {
    foo = 42; // 永遠(yuǎn)也不會運(yùn)行
}
console.log(foo); // HTML元素

你可能認(rèn)為只有JavaScript代碼才能創(chuàng)建全局變量,并且習(xí)慣使用typeof或..in window來檢測全局變量。但是如上例所示,HTML頁面中的內(nèi)容也會產(chǎn)生全局變量,并且稍不注意就很容易讓全局變量檢查錯誤百出。
這也是盡量不要使用全局變量的一個原因。如果確實(shí)要用,也要確保變量名的唯一性,從而避免與其他地方的變量產(chǎn)生沖突,包括HTML和其他第三方代碼。

原生原型
一個廣為人知的JavaScript的最佳實(shí)踐是:不要擴(kuò)展原生原型。
如果向Array.prototype中加入新的方法和屬性,假設(shè)它們確實(shí)有用,設(shè)計和命名都很得當(dāng),那它最后很有可能會被加入到JavaScript規(guī)范當(dāng)中。這樣一來你所做的擴(kuò)展就會與之沖突。
所以,首先不要擴(kuò)展原生方法,除非你確信代碼在運(yùn)行環(huán)境中不會有沖突。如果對此你并非100%確定,那么進(jìn)行擴(kuò)展是非常危險的。這需要你自己仔細(xì)權(quán)衡利弊。
其次,在擴(kuò)展原生方法時需要加入判斷條件(因?yàn)槟憧赡軣o意中覆蓋了原來的方法)。對于前面的例子,下面的處理方式要更好一些:

if (!Array.prototype.push) {
    // Netscape 4沒有Array.push
    Array.prototype.push = function (item) {
        this[this.length - 1] = item;
    };
}

shim/polyfill
polyfill(或者shim)能有效地為不符合最新規(guī)范的老版本瀏覽器填補(bǔ)缺失的功能,讓你能夠通過可靠的代碼來支持所有你想要支持的運(yùn)行環(huán)境。

ES5-Shim(https://github.com/es-shims/es5-shim) 是一個完整的shim/polyfill
集合,能夠?yàn)槟愕捻?xiàng)目提供ES5 基本規(guī)范支持。同樣,ES6-Shim(https://
github.com/es-shims/es6-shim)提供了對ES6 基本規(guī)范的支持。雖然我們可
以通過shim/polyfill 來填補(bǔ)新的API,但是無法填補(bǔ)新的語法??梢允褂?br> Traceur(https://github.com/google/traceur-compiler/wiki/GettingStarted) 這樣
的工具來實(shí)現(xiàn)新舊語法之間的轉(zhuǎn)換。

對于將來可能成為標(biāo)準(zhǔn)的功能,按照大部分人贊同的方式來預(yù)先實(shí)現(xiàn)能和將來的標(biāo)準(zhǔn)兼容的polyfill,我們成為prollyfill(probably fill)。

<script>
絕大部分網(wǎng)站/Web應(yīng)用程序的代碼都存放在多個文件中,通??梢栽诰W(wǎng)頁中使用<scriopt src=..></script>來加載這些文件,或者使用<script> .. </script>來包含內(nèi)斂代碼。
這些文件和內(nèi)聯(lián)代碼是相互獨(dú)立的JavaScript程序還是一個整體呢?
答案是它們的運(yùn)行方式更像是相互獨(dú)立的JavaScript程序,但是并非總是如此。
它們共享global對象(在瀏覽器中在是window),也就是說這些文件中的代碼在共享的命名空間中運(yùn)行,并相互交互。
如果某個script中定義了函數(shù)foo(),后面的script代碼就可以訪問并調(diào)用foo(),就像foo()在其內(nèi)部被聲明過一樣。
但是全局變量作用域的提升機(jī)制在這些便捷中不適用,因此無論是<script> .. </script>還是<script src=..></script>,下面的代碼都無法運(yùn)行(因?yàn)閒oo()還未被聲明):

<script>foo();</script>
<script>
    function foo() { .. }
</script>

但是下面的兩端代碼則沒問題:

<script>
    foo();
    function foo() { .. }
</script>

和:

<script>
    function foo() { .. }
</script>
<script>foo();</script>

如果script中的代碼(無論是內(nèi)聯(lián)代碼還是外部代碼)發(fā)生錯誤,它會像獨(dú)立的JavaScript程序那樣停止,但是后續(xù)的script中的代碼(仍然共享global)依然會接著運(yùn)行,不會受影響。
你可以使用代碼來動態(tài)創(chuàng)建script,將其加入到頁面的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è)置為一個文件URL,就可以通過<script src=""></script>動態(tài)加載外部文件。

內(nèi)聯(lián)代碼和外部文件中的代碼之間有一個區(qū)別,即在內(nèi)聯(lián)代碼中不可以出現(xiàn)</script>字符串,一旦出現(xiàn)即被視為代碼結(jié)束。因此對于下面這樣的代碼需要非常小心:

<script>
    var code = "<script>alert( ‘Hello World’ )</script>";
</script>

上述代碼看似沒什么問題,但是字符串常量中的</script>將會被當(dāng)作結(jié)束標(biāo)簽來處理,因此會導(dǎo)致錯誤。常用的變通方法是:

"</sc" + "ript>";

另外需要注意的一點(diǎn)是,我們是根據(jù)代碼文件的字符集屬性(UTF-8、ISO-8859-8等)來解析外部文件中的代碼(或者默認(rèn)字符集),而內(nèi)聯(lián)代碼則使用其所在頁面文件的字符集(或者默認(rèn)字符集)。

現(xiàn)實(shí)中的限制
JavaScript規(guī)范對于函數(shù)中參數(shù)的個數(shù),以及字符串常量的長度等并沒有限制;但是由于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引擎中你會得到正確答案499950000,而另外一些引擎(如Safari 6.x)中則會產(chǎn)生錯誤“RangeError: Maximum call stack size exceeded”。
下面列出一些已知的限制:

  • 字符串常量中允許的最大字符數(shù)(并非只是針對字符串值);
  • 可以作為參數(shù)傳遞到函數(shù)中的數(shù)據(jù)大?。ㄒ卜Q為棧大小,以字節(jié)為單位);
  • 函數(shù)聲明中的參數(shù)個數(shù);
  • 未經(jīng)優(yōu)化的調(diào)用棧(例如遞歸)的最大層數(shù),即函數(shù)調(diào)用鏈的最大長度;
  • JavaScript程序以阻塞方式在瀏覽器中運(yùn)行的最長時間(秒);
  • 變量名的最大長度。
好想每天都沒有什么事情做,但又害怕每天沒有什么事情做
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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