關(guān)于在我的前端文集中發(fā)表的你不知道的JavaScript(中卷)|xx,是我在閱讀《你不知道的JavaScript》(中卷)時對知識點(diǎn)的摘抄撰茎。
內(nèi)置類型
JavaScript有七種內(nèi)置類型:
- 空值(null)
- 未定義(undefined)
- 布爾值(boolean)
- 數(shù)字(number)
- 字符串(string)
- 對象(object)
- 符號(symbol嗽冒,ES6中新增)
除對象之外捌显,其他統(tǒng)稱為“基本類型”。
我們可以用typeof運(yùn)算符來查看值的類型农尖,它返回的是類型的字符串值屉符。有意思的是剧浸,這七種類型和它們的字符串值并不一一對應(yīng):
typeof undefined === "undefined"; // true
typeof true === "boolean"; // true
typeof 42 === "number"; // true
typeof "42" === "string"; // true
typeof { life: 42 } === "object"; // true
// ES6中新加入的類型
typeof Symbol() === "symbol"; // true
你可能注意到null類型不在此列。它比較特殊矗钟,typeof對它的處理有問題:
typeof null === "object"; // true
正確的返回結(jié)果應(yīng)該是“null”唆香,但這個bug由來已久,在JavaScript中已經(jīng)存在了將近二十年吨艇,也許永遠(yuǎn)也不會修復(fù)躬它,因?yàn)檫@牽涉到太多的Web系統(tǒng),“修復(fù)”它會產(chǎn)生更多的bug东涡,令許多系統(tǒng)無法正常工作。
我們需要使用復(fù)合條件來檢測null值的類型:
var a = null;
(!a && typeof a === "object"); // true
還有一種情況:
typeof function a(){ /* .. */ } === "function"; // true
這樣看來疮跑,function(函數(shù))也是JavaScript的一個內(nèi)置類型。然而查閱規(guī)范就會知道祖娘,它實(shí)際上是object的一個“子類型”。具體來說,函數(shù)是“可調(diào)用對象”掀潮,它有一個內(nèi)部屬性[[Call]]菇夸,該屬性使其可以被調(diào)用仪吧。
函數(shù)不僅是對象庄新,還可以擁有屬性薯鼠。例如:
function a(b, c) {
/* .. */
}
函數(shù)對象的length屬性是其聲明的參數(shù)的個數(shù):
a.length;//2
因?yàn)樵摵瘮?shù)聲明了兩個命名參數(shù)摄咆,b和c,所以其length值為2人断。
再來看看數(shù)組。JavaScript支持?jǐn)?shù)組:
typeof [1,2,3] === "object"; // true
數(shù)組也是對象恶迈。確切地說谱醇,它也是object的一個“子類型”暇仲,數(shù)組的元素按數(shù)字順序來進(jìn)行索引(而非普通像對象那樣通過字符串鍵值)副渴,其length屬性是元素的個數(shù)。
值和類型
JavaScript中的變量是沒有類型的煮剧,只有值才有。變量可以隨時持有任何類型的值勉盅。
undefined和undeclared
變量在未持有值的時候?yàn)閡ndefined。此時typeof返回“undefined”挑胸。
大多數(shù)開發(fā)者傾向于將undefined等同于undeclared(未聲明)宰闰,但在JavaScript中它們完全是兩回事。
已在作用域中聲明但還沒有賦值的變量移袍,是undefined的。相反舆逃,還沒有在作用域中聲明過的變量,是undeclared的路狮。
var a;
a; // undefined
b; // ReferenceError: b is not defined
var a;
typeof a; // "undefined"
typeof b; // "undefined"
對于undeclared(或者not defined)變量,typeof照樣返回“undefined”涂籽。請注意雖然b是一個undeclared變量,但typeof b并沒有報錯砸抛。這是因?yàn)閠ypeof有一個特殊的安全防范機(jī)制。
typeof Undeclared
該安全防范機(jī)制對在瀏覽器中運(yùn)行的JavaScript代碼來說還是很有幫助的景东,因?yàn)槎鄠€腳本文件會在共享的全局命名空間中加載變量奔誓。
舉個簡單的例子,在程序中使用全局變量DEBUG作為“調(diào)試模式”的開關(guān)和措。在輸出調(diào)試信息到控制臺之前蜕煌,我們會檢查DEBUG變量是否已被聲明。頂層的全局變量聲明var DEBUG=true只在debug.js文件中才有斜纪,而該文件只在開發(fā)和測試時才被加載到瀏覽器,在生產(chǎn)環(huán)境中不予加載颁独。
問題是如何在程序中檢查全局變量DEBUG才不會出現(xiàn)ReferenceError錯誤伪冰。這時typeof的安全防范機(jī)制就成了我們的好幫手:
// 這樣會拋出錯誤
if (DEBUG) {
console.log("Debugging is starting");
}
// 這樣是安全的
if (typeof DEBUG !== "undefined") {
console.log("Debugging is starting");
}
這不僅對用戶定義的變量(比如DEBUG)有用,對內(nèi)建的API也有幫助:
if (typeof atob === "undefined") {
atob = function() { /*..*/ };
}
如果要為某個缺失的功能寫polyfill(即襯墊代碼或補(bǔ)充代碼靠柑,用來補(bǔ)充當(dāng)前運(yùn)行環(huán)境中缺失的功能)吓懈,一般不會用var atob來聲明變量atob。如果在if語句中使用var atob耻警,聲明會被提升到作用域(即當(dāng)前腳本或函數(shù)的作用域)的最頂層甸怕,即使if條件不成立也是如此(因?yàn)閍tob全局變量已經(jīng)存在)腮恩。在有些瀏覽器中,對于一些特殊的內(nèi)建全局變量(通常稱為“宿主對象”)武契,這樣的重復(fù)聲明會報錯荡含。去掉var則可以防止聲明被提升。
還有一種不用通過typeof的安全防范機(jī)制的方法全释,就是檢查所有全局變量是否是全局對象的屬性误债,瀏覽器中的全局對象是window。所以前面的例子也可以這樣來實(shí)現(xiàn):
if (window.DEBUG) {
// ..
}
if (!window.atob) {
// ..
}
與undeclared變量不同,訪問不存在的對象屬性(甚至是在全局對象window上)不會產(chǎn)生ReferenceError錯誤判族。
一些開發(fā)人員不喜歡通過window來訪問全局對象,尤其當(dāng)代碼需要運(yùn)行在多種JavaScript環(huán)境中時(不僅僅是瀏覽器槽惫,還有服務(wù)器端辩撑,如node.js等),因此此時全局對象并非總是window合冀。
從技術(shù)角度來說,typeof的安全防范機(jī)制對于非全局變量也很管用峭判,雖然這種情況并不多見棕叫,也有一些開發(fā)人員不大愿意這樣做。如果想讓別人在他們的程序或模塊中復(fù)制黏貼你的代碼疗认,就需要檢查你用到的變量是否已經(jīng)在宿主程序中定義過:
function doSomethingCool() {
var helper =
(typeof FeatureXYZ !== "undefined") ?
FeatureXYZ :
function () { /*.. default feature ..*/ };
var val = helper();
// ..
}
還有一些人喜歡使用“依賴注入”設(shè)計(jì)模式,就是將依賴通過參數(shù)顯示地傳遞到函數(shù)中横漏,如:
function doSomethingCool(FeatureXYZ) {
var helper = FeatureXYZ ||
function () { /*.. default feature ..*/ };
var val = helper();
// ..
}