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

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

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

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

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

在針對運行環境進行編碼時,宿主對象扮演著一個十分關鍵的角色,但要特別注意其行為特性,因為它們常常有別于普通的JavaScript object。
在我們經常打交道的宿主對象中,console及其各種方法(log(..)、error(..)等)是比較值得一提的。console對象由宿主環境提供,以便從代碼中輸出各種值。
console在瀏覽器中是輸出到開發工具控制臺,而在Node.js和其他服務器JavaScript環境中,則是指向JavaScript環境系統進程的標準輸出(stdout)和標準錯誤輸出(stderr)。

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

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

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

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

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

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

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

對于將來可能成為標準的功能,按照大部分人贊同的方式來預先實現能和將來的標準兼容的polyfill,我們成為prollyfill(probably fill)。

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

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

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

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

和:

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

如果script中的代碼(無論是內聯代碼還是外部代碼)發生錯誤,它會像獨立的JavaScript程序那樣停止,但是后續的script中的代碼(仍然共享global)依然會接著運行,不會受影響。
你可以使用代碼來動態創建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的值設置為一個文件URL,就可以通過<script src=""></script>動態加載外部文件。

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

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

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

"</sc" + "ript>";

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

現實中的限制
JavaScript規范對于函數中參數的個數,以及字符串常量的長度等并沒有限制;但是由于JavaScript引擎實現各異,規范在某些地方有一些限制。

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); // 應該是: 499950000

在一些JavaScript引擎中你會得到正確答案499950000,而另外一些引擎(如Safari 6.x)中則會產生錯誤“RangeError: Maximum call stack size exceeded”。
下面列出一些已知的限制:

  • 字符串常量中允許的最大字符數(并非只是針對字符串值);
  • 可以作為參數傳遞到函數中的數據大小(也稱為棧大小,以字節為單位);
  • 函數聲明中的參數個數;
  • 未經優化的調用棧(例如遞歸)的最大層數,即函數調用鏈的最大長度;
  • JavaScript程序以阻塞方式在瀏覽器中運行的最長時間(秒);
  • 變量名的最大長度。
好想每天都沒有什么事情做,但又害怕每天沒有什么事情做
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,702評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,143評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,553評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,620評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,416評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,940評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,024評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,170評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,709評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,597評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,291評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,029評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,407評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,663評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,403評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,746評論 2 370

推薦閱讀更多精彩內容