特別說明,為便于查閱坑鱼,文章轉(zhuǎn)自https://github.com/getify/You-Dont-Know-JS
在第一和第二章中枷遂,我們幾次提到了各種內(nèi)建類型,通常稱為“原生類型”创译,比如 String
和 Number
。現(xiàn)在讓我們來仔細(xì)檢視它們墙基。
這是最常用的原生類型的一覽:
String()
Number()
Boolean()
Array()
Object()
Function()
RegExp()
Date()
Error()
-
Symbol()
—— 在 ES6 中被加入的软族!
如你所見辛藻,這些原生類型實(shí)際上是內(nèi)建函數(shù)。
如果你擁有像 Java 語言那樣的背景互订,JavaScript 的 String()
看起來像是你曾經(jīng)用來創(chuàng)建字符串值的 String(..)
構(gòu)造器吱肌。所以,你很快就會觀察到你可以做這樣的事情:
var s = new String( "Hello World!" );
console.log( s.toString() ); // "Hello World!"
這些原生類型的每一種確實(shí)可以被用作一個原生類型的構(gòu)造器仰禽。但是被構(gòu)建的東西可能與你想象的不同:
var a = new String( "abc" );
typeof a; // "object" ... 不是 "String"
a instanceof String; // true
Object.prototype.toString.call( a ); // "[object String]"
創(chuàng)建值的構(gòu)造器形式(new String("abc")
)的結(jié)果是一個基本類型值("abc"
)的包裝器對象氮墨。
重要的是,typeof
顯示這些對象不是它們自己的特殊 類型吐葵,而是 object
類型的子類型规揪。
這個包裝器對象可以被進(jìn)一步觀察,像這樣:
console.log( a );
這個語句的輸出會根據(jù)你使用的瀏覽器變化温峭,因?yàn)閷τ陂_發(fā)者的查看猛铅,開發(fā)者控制臺可以自由選擇它認(rèn)為合適的方式來序列化對象。
注意: 在寫作本書時凤藏,最新版的 Chrome 打印出這樣的東西:String {0: "a", 1: "b", 2: "c", length: 3, [[PrimitiveValue]]: "abc"}
奸忽。但是老版本的 Chrome 曾經(jīng)只打印出這些:String {0: "a", 1: "b", 2: "c"}
。當(dāng)前最新版的 Firefox 打印 String ["a","b","c"]
揖庄,但它曾經(jīng)以斜體字打印 "abc"
栗菜,點(diǎn)擊它可以打開對象查看器。當(dāng)然蹄梢,這些結(jié)果是總頻繁變更的疙筹,而且你的體驗(yàn)也許不同。
重點(diǎn)是禁炒,new String("abc")
為 "abc"
創(chuàng)建了一個字符串包裝器對象而咆,而不僅是基本類型值 "abc"
本身。
內(nèi)部 [[Class]]
typeof
的結(jié)果為 "object"
的值(比如數(shù)組)被額外地打上了一個內(nèi)部的標(biāo)簽屬性 [[Class]]
(請把它考慮為一個內(nèi)部的分類方法幕袱,而非與傳統(tǒng)的面向?qū)ο缶幋a的類有關(guān))暴备。這個屬性不能直接地被訪問,但通嘲挤洌可以間接地通過在這個值上借用默認(rèn)的 Object.prototype.toString(..)
方法調(diào)用來展示馍驯。舉例來說:
Object.prototype.toString.call( [1,2,3] ); // "[object Array]"
Object.prototype.toString.call( /regex-literal/i ); // "[object RegExp]"
所以,對于這個例子中的數(shù)組來說玛痊,內(nèi)部的 [[Class]]
值是 "Array"
,而對于正則表達(dá)式狂打,它是 "RegExp"
擂煞。在大多數(shù)情況下,這個內(nèi)部的 [[Class]]
值對應(yīng)于關(guān)聯(lián)這個值的內(nèi)建的原生類型構(gòu)造器(見下面的討論)趴乡,但事實(shí)卻不總是這樣对省。
基本類型呢蝗拿?首先,null
和 undefined
:
Object.prototype.toString.call( null ); // "[object Null]"
Object.prototype.toString.call( undefined ); // "[object Undefined]"
你會注意到蒿涎,不存在 Null()
和 Undefined()
原生類型構(gòu)造器哀托,但不管怎樣 "Null"
和 "Undefined"
是被暴露出來的內(nèi)部 [[Class]]
值。
但是對于像 string
劳秋、number
仓手、和 boolean
這樣的簡單基本類型,實(shí)際上會啟動另一種行為玻淑,通常稱為“封箱(boxing)”(見下一節(jié)“封箱包裝器”):
Object.prototype.toString.call( "abc" ); // "[object String]"
Object.prototype.toString.call( 42 ); // "[object Number]"
Object.prototype.toString.call( true ); // "[object Boolean]"
在這個代碼段中嗽冒,每一個簡單基本類型都自動地被它們分別對應(yīng)的對象包裝器封箱,這就是為什么 "String"
补履、"Number"
添坊、和 "Boolean"
分別被顯示為內(nèi)部 [[Class]]
值。
注意: 從 ES5 發(fā)展到 ES6 的過程中箫锤,這里展示的 toString()
和 [[Class]]
的行為發(fā)生了一點(diǎn)兒改變贬蛙,但我們會在本系列的 ES6 與未來 一書中講解它們的細(xì)節(jié)。
封箱包裝器
這些對象包裝器服務(wù)于一個非常重要的目的谚攒∷倏停基本類型值沒有屬性或方法,所以為了訪問 .length
或 .toString()
你需要這個值的對象包裝器五鲫。值得慶幸的是溺职,JS 將會自動地 封箱(也就是包裝)基本類型值來滿足這樣的訪問。
var a = "abc";
a.length; // 3
a.toUpperCase(); // "ABC"
那么位喂,如果你想以通常的方式訪問這些字符串值上的屬性/方法浪耘,比如一個 for
循環(huán)的 i < a.length
條件,這么做看起來很有道理:一開始就得到一個這個值的對象形式塑崖,于是 JS 引擎就不需要隱含地為你創(chuàng)建一個七冲。
但事實(shí)證明這是一個壞主意。瀏覽器們長久以來就對 .length
這樣的常見情況進(jìn)行性能優(yōu)化规婆,這意味著如果你試著直接使用對象形式(它們沒有被優(yōu)化過)進(jìn)行“提前優(yōu)化”澜躺,那么實(shí)際上你的程序?qū)?變慢。
一般來說抒蚜,基本上沒有理由直接使用對象形式掘鄙。讓封箱在需要的地方隱含地發(fā)生會更好。換句話說嗡髓,永遠(yuǎn)也不要做 new String("abc")
操漠、new Number(42)
這樣的事情 —— 應(yīng)當(dāng)總是偏向于使用基本類型字面量 "abc"
和 42
。
對象包裝器的坑
如果你 確實(shí) 選擇要直接使用對象包裝器饿这,那么有幾個坑你應(yīng)該注意浊伙。
舉個例子撞秋,考慮 Boolean
包裝的值:
var a = new Boolean( false );
if (!a) {
console.log( "Oops" ); // 永遠(yuǎn)不會運(yùn)行
}
這里的問題是,雖然你為值 false
創(chuàng)建了一個對象包裝器嚣鄙,但是對象本身是“truthy”(見第四章)吻贿,所以使用對象的效果是與使用底層的值 false
本身相反的,這與通常的期望十分不同哑子。
如果你想手動封箱一個基本類型值舅列,你可以使用 Object(..)
函數(shù)(沒有 new
關(guān)鍵字):
var a = "abc";
var b = new String( a );
var c = Object( a );
typeof a; // "string"
typeof b; // "object"
typeof c; // "object"
b instanceof String; // true
c instanceof String; // true
Object.prototype.toString.call( b ); // "[object String]"
Object.prototype.toString.call( c ); // "[object String]"
再說一遍,通常不鼓勵直接使用封箱的包裝器對象(比如上面的 b
和 c
)赵抢,但你可能會遇到一些它們有用的罕見情況剧蹂。
開箱
如果你有一個包裝器對象,而你想要取出底層的基本類型值烦却,你可以使用 valueOf()
方法:
var a = new String( "abc" );
var b = new Number( 42 );
var c = new Boolean( true );
a.valueOf(); // "abc"
b.valueOf(); // 42
c.valueOf(); // true
當(dāng)以一種查詢基本類型值的方式使用對象包裝器時宠叼,開箱也會隱含地發(fā)生。這個處理的過程(強(qiáng)制轉(zhuǎn)換)將會在第四章中更詳細(xì)地講解其爵,但簡單地說:
var a = new String( "abc" );
var b = a + ""; // `b` 擁有開箱后的基本類型值"abc"
typeof a; // "object"
typeof b; // "string"
原生類型作為構(gòu)造器
對于 array
冒冬、object
、function
和正則表達(dá)式值來說摩渺,使用字面形式來創(chuàng)建它們的值幾乎總是更好的選擇简烤,而且字面形式與構(gòu)造器形式所創(chuàng)建的值是同一種對象(也就是,沒有非包裝的值)摇幻。
正如我們剛剛在上面看到的其他原生類型横侦,除非你真的知道你需要這些構(gòu)造器形式,一般來說應(yīng)當(dāng)避免使用它們绰姻,這主要是因?yàn)樗鼈儠硪恍┠憧赡懿粫胍獙Ω兜漠惓:拖葳濉?/p>
Array(..)
var a = new Array( 1, 2, 3 );
a; // [1, 2, 3]
var b = [1, 2, 3];
b; // [1, 2, 3]
注意: Array(..)
構(gòu)造器不要求在它前面使用 new
關(guān)鍵字枉侧。如果你省略它,它也會像你已經(jīng)使用了一樣動作狂芋。所以 Array(1,2,3)
和 new Array(1,2,3)
的結(jié)果是一樣的榨馁。
Array
構(gòu)造器有一種特殊形式,如果它僅僅被傳入一個 number
參數(shù)帜矾,與將這個值作為數(shù)組的 內(nèi)容 不同翼虫,它會被認(rèn)為是用來“預(yù)定數(shù)組大小”(嗯,某種意義上)用的長度屡萤。
這是個可怕的主意珍剑。首先,你會意外地用錯這種形式灭衷,因?yàn)樗苋菀淄洝?/p>
但更重要的是次慢,其實(shí)沒有預(yù)定數(shù)組大小這樣的東西。你所創(chuàng)建的是一個空數(shù)組翔曲,并將這個數(shù)組的 length
屬性設(shè)置為那個指定的數(shù)字值迫像。
一個數(shù)組在它的值槽上沒有明確的值,但是有一個 length
屬性意味著這些值槽是存在的瞳遍,在 JS 中這是一個詭異的數(shù)據(jù)結(jié)構(gòu)闻妓,它帶有一些非常奇怪且令人困惑的行為÷有担可以創(chuàng)建這樣的值的能力由缆,完全源自于老舊的、已經(jīng)廢棄的猾蒂、僅具有歷史意義的功能(比如arguments
這樣的“類數(shù)組對象”)均唉。
注意: 帶有至少一個“空值槽”的數(shù)組經(jīng)常被稱為“稀散數(shù)組”。
這是另外一個例子肚菠,展示瀏覽器的開發(fā)者控制臺在如何表示這樣的對象上有所不同舔箭,它產(chǎn)生了更多的困惑。
舉例來說:
var a = new Array( 3 );
a.length; // 3
a;
在 Chrome 中 a
的序列化表達(dá)是(在本書寫作時):[ undefined x 3 ]
蚊逢。這真的很不幸层扶。 它暗示著在這個數(shù)組的值槽中有三個 undefined
值,而事實(shí)上這樣的值槽是不存在的(所謂的“空值槽(empty slots)” —— 也是一個爛名字@雍伞)镜会。
要觀察這種不同,試試這段代碼:
var a = new Array( 3 );
var b = [ undefined, undefined, undefined ];
var c = [];
c.length = 3;
a;
b;
c;
注意: 正如你在這個例子中看到的 c
终抽,數(shù)組中的空值槽可以在數(shù)組的創(chuàng)建之后發(fā)生戳表。將數(shù)組的 length
改變?yōu)槌^它實(shí)際定義的槽值的數(shù)目,你就隱含地引入了空值槽昼伴。事實(shí)上匾旭,你甚至可以在上面的代碼段中調(diào)用 delete b[1]
,而這么做將會在 b
的中間引入一個空值槽亩码。
對于 b
(在當(dāng)前的 Chrome 中)季率,你會發(fā)現(xiàn)它的序列化表現(xiàn)為 [ undefined, undefined, undefined ]
,與之相對的是 a
和 c
的 [ undefined x 3 ]
描沟。糊涂了吧飒泻?是的,大家都糊涂了吏廉。
更糟糕的是泞遗,在寫作本書時,F(xiàn)irefox 對 a
和 c
報告 [ , , , ]
席覆。你發(fā)現(xiàn)為什么這使人犯糊涂了嗎史辙?仔細(xì)看。三個逗號表示有四個值槽,不是我們期望的三個值槽聊倔。
什么;薇小? Firefox 在它們的序列化表達(dá)的末尾放了一個額外的 ,
耙蔑,因?yàn)樵?ES5 中见妒,列表(數(shù)組值,屬性列表等等)末尾的逗號是允許的(被砍掉并忽略)甸陌。所以如果你在你的程序或控制臺中敲入 [ , , , ]
值须揣,你實(shí)際上得到的是一個底層為 [ , , ]
的值(也就是,一個帶有三個空值槽的數(shù)組)钱豁。這種選擇耻卡,雖然在閱讀開發(fā)者控制臺時使人困惑,但是因?yàn)樗箍截愓迟N的時候準(zhǔn)確牲尺,所以被留了下來卵酪。
如果你現(xiàn)在在搖頭或翻白眼兒,你并不孤單=招弧(聳肩)
不幸的是凛澎,事情越來越糟。比在控制臺的輸出產(chǎn)生的困惑更糟的是估蹄,上面代碼段中的 a
和 b
實(shí)際上在有些情況下相同塑煎,但在另一些情況下不同:
a.join( "-" ); // "--"
b.join( "-" ); // "--"
a.map(function(v,i){ return i; }); // [ undefined x 3 ]
b.map(function(v,i){ return i; }); // [ 0, 1, 2 ]
呃。
a.map(..)
調(diào)用會 失敗 是因?yàn)橹挡鄹揪筒粚?shí)際存在臭蚁,所以 map(..)
沒有東西可以迭代最铁。join(..)
的工作方式不同,基本上我們可以認(rèn)為它是像這樣被實(shí)現(xiàn)的:
function fakeJoin(arr,connector) {
var str = "";
for (var i = 0; i < arr.length; i++) {
if (i > 0) {
str += connector;
}
if (arr[i] !== undefined) {
str += arr[i];
}
}
return str;
}
var a = new Array( 3 );
fakeJoin( a, "-" ); // "--"
如你所見垮兑,join(..)
好用僅僅是因?yàn)樗?認(rèn)為 值槽存在冷尉,并循環(huán)至 length
值。不管 map(..)
內(nèi)部是在做什么系枪,它(顯然)沒有做出這樣的假設(shè)雀哨,所以源自于奇怪的“空值槽”數(shù)組的結(jié)果出人意料,而且好像是失敗了私爷。
那么雾棺,如果你想要 確實(shí) 創(chuàng)建一個實(shí)際的 undefined
值的數(shù)組(不只是“空值槽”),你如何才能做到呢(除了手動以外)衬浑?
var a = Array.apply( null, { length: 3 } );
a; // [ undefined, undefined, undefined ]
糊涂了吧捌浩?是的。這里是它大概的工作方式工秩。
apply(..)
是一個對所有函數(shù)可用的工具方法尸饺,它以一種特殊方式調(diào)用這個使用它的函數(shù)进统。
第一個參數(shù)是一個 this
對象綁定(在本系列的 this 與對象原型 中有詳細(xì)講解),在這里我們不關(guān)心它浪听,所以我們將它設(shè)置為 null
螟碎。第二個參數(shù)應(yīng)該是一個數(shù)組(或 像 數(shù)組的東西 —— 也就是“類數(shù)組對象”)。這個“數(shù)組”的內(nèi)容作為這個函數(shù)的參數(shù)“擴(kuò)散”開來馋辈。
所以抚芦,Array.apply(..)
在調(diào)用 Array(..)
函數(shù)倍谜,并將一個值({ length: 3 }
對象值)作為它的參數(shù)值擴(kuò)散開迈螟。
在 apply(..)
內(nèi)部,我們可以預(yù)見這里有另一個 for
循環(huán)(有些像上面的 join(..)
)尔崔,它從 0
開始上升但不包含至 length
(這個例子中是 3
)答毫。
對于每一個索引,它從對象中取得相應(yīng)的鍵季春。所以如果這個數(shù)組對象參數(shù)在 apply(..)
內(nèi)部被命名為 arr
洗搂,那么這種屬性訪問實(shí)質(zhì)上是arr[0]
、arr[1]
和 arr[2]
载弄。當(dāng)然耘拇,沒有一個屬性是在 { length: 3 }
對象值上存在的,所以這三個屬性訪問都將返回值 undefined
宇攻。
換句話說惫叛,調(diào)用 Array(..)
的結(jié)局基本上是這樣:Array(undefined,undefined,undefined)
,這就是我們?nèi)绾蔚玫揭粋€填滿 undefined
值的數(shù)組的逞刷,而非僅僅是一些(瘋狂的)空值槽嘉涌。
雖然對于創(chuàng)建一個填滿 undefined
值的數(shù)組來說,Array.apply( null, { length: 3 } )
是一個奇怪而且繁冗的方法夸浅,但是它要比使用砸自己的腳似的 Array(3)
空值槽要可靠和好得 太多了仑最。
底線:你 在任何情況下,永遠(yuǎn)不帆喇,也不應(yīng)該有意地創(chuàng)建并使用詭異的空值槽數(shù)組警医。就別這么干。它們是怪胎坯钦。
Object(..)
预皇、Function(..)
和 RegExp(..)
Object(..)
/Function(..)
/RegExp(..)
構(gòu)造器一般來說也是可選的(因此除非是特別的目的,應(yīng)當(dāng)避免使用):
var c = new Object();
c.foo = "bar";
c; // { foo: "bar" }
var d = { foo: "bar" };
d; // { foo: "bar" }
var e = new Function( "a", "return a * 2;" );
var f = function(a) { return a * 2; };
function g(a) { return a * 2; }
var h = new RegExp( "^a*b+", "g" );
var i = /^a*b+/g;
幾乎沒有理由使用 new Object()
構(gòu)造器形式葫笼,尤其因?yàn)樗鼜?qiáng)迫你一個一個地添加屬性深啤,而不是像對象的字面形式那樣一次添加許多。
Function
構(gòu)造器僅在最最罕見的情況下有用路星,也就是你需要動態(tài)地定義一個函數(shù)的參數(shù)和/或它的函數(shù)體溯街。不要將 Function(..)
僅僅作為另一種形式的 eval(..)
诱桂。你幾乎永遠(yuǎn)不會需要用這種方式動態(tài)定義一個函數(shù)。
用字面量形式(/^a*b+/g
)定義正則表達(dá)式是被大力采用的呈昔,不僅因?yàn)檎Z法簡單挥等,而且還有性能的原因 —— JS 引擎會在代碼執(zhí)行前預(yù)編譯并緩存它們。和我們迄今看到的其他構(gòu)造器形式不同堤尾,RegExp(..)
有一些合理的用途:用來動態(tài)定義一個正則表達(dá)式的范例肝劲。
var name = "Kyle";
var namePattern = new RegExp( "\\b(?:" + name + ")+\\b", "ig" );
var matches = someText.match( namePattern );
這樣的場景在 JS 程序中一次又一次地合法出現(xiàn),所以你有需要使用 new RegExp("pattern","flags")
形式郭宝。
Date(..)
和 Error(..)
Date(..)
和 Error(..)
原生類型構(gòu)造器要比其他種類的原生類型有用得多辞槐,因?yàn)樗鼈儧]有字面量形式。
要創(chuàng)建一個日期對象值粘室,你必須使用 new Date()
榄檬。Date(..)
構(gòu)造器接收可選參數(shù)值來指定要使用的日期/時間,但是如果省略的話衔统,就會使用當(dāng)前的日期/時間鹿榜。
目前你構(gòu)建一個日期對象的最常見的理由是要得到當(dāng)前的時間戳(一個有符號整數(shù),從1970年1月1日開始算起的毫秒數(shù))锦爵。你可以在一個日期對象實(shí)例上調(diào)用 getTime()
得到它舱殿。
但是在 ES5 中,一個更簡單的方法是調(diào)用定義為 Date.now()
的靜態(tài)幫助函數(shù)险掀。而且在前 ES5 中填補(bǔ)它很容易:
if (!Date.now) {
Date.now = function(){
return (new Date()).getTime();
};
}
注意: 如果你不帶 new
調(diào)用 Date()
沪袭,你將會得到一個那個時刻的日期/時間的字符串表達(dá)。在語言規(guī)范中沒有規(guī)定這個表達(dá)的確切形式迷郑,雖然各個瀏覽器趨向于贊同使用這樣的東西:"Fri Jul 18 2014 00:31:02 GMT-0500 (CDT)"
枝恋。
Error(..)
構(gòu)造器(很像上面的 Array()
)在有 new
與沒有 new
時的行為是相同的。
你想要創(chuàng)建 error 對象的主要原因是,它會將當(dāng)前的執(zhí)行棧上下文捕捉進(jìn)對象中(在大多數(shù) JS 引擎中,在創(chuàng)建后使用只讀的 .stack
屬性表示)垃僚。這個棧上下文包含函數(shù)調(diào)用棧和 error 對象被創(chuàng)建時的行號,這使調(diào)試這個錯誤更簡單十电。
典型地,你將與 throw
操作符一起使用這樣的 error 對象:
function foo(x) {
if (!x) {
throw new Error( "x wasn't provided" );
}
// ..
}
Error 對象實(shí)例一般擁有至少一個 message
屬性叹螟,有時還有其他屬性(你應(yīng)當(dāng)將它們作為只讀的)鹃骂,比如 type
。然而罢绽,與其檢視上面提到的 stack
屬性畏线,最好是在 error 對象上調(diào)用 toString()
(明確地調(diào)用,或者是通過強(qiáng)制轉(zhuǎn)換隱含地調(diào)用 —— 見第四章)來得到一個格式友好的錯誤消息良价。
提示: 技術(shù)上講寝殴,除了一般的 Error(..)
原生類型以外蒿叠,還有幾種特定錯誤的原生類型:EvalError(..)
、RangeError(..)
蚣常、ReferenceError(..)
市咽、SyntaxError(..)
、TypeError(..)
和 URIError(..)
抵蚊。但是手動使用這些特定錯誤原生類型十分少見施绎。如果你的程序確實(shí)遭受了一個真實(shí)的異常,它們是會自動地被使用的(比如引用一個未聲明的變量而得到一個 ReferenceError
錯誤)贞绳。
Symbol(..)
在 ES6 中谷醉,新增了一個基本值類型,稱為“Symbol”熔酷。Symbol 是一種特殊的“獨(dú)一無二”(不是嚴(yán)格保證的!)的值孤紧,可以作為對象上的屬性使用而幾乎不必?fù)?dān)心任何沖突。它們主要是為特殊的 ES6 結(jié)構(gòu)的內(nèi)建行為設(shè)計(jì)的拒秘,但你也可以定義你自己的 symbol。
Symbol 可以用做屬性名臭猜,但是你不能從你的程序中看到或訪問一個 symbol 的實(shí)際值躺酒,從開發(fā)者控制臺也不行。例如蔑歌,如果你在開發(fā)者控制臺中對一個 Symbol 求值羹应,將會顯示 Symbol(Symbol.create)
之類的東西。
在 ES6 中有幾種預(yù)定義的 Symbol次屠,做為 Symbol
函數(shù)對象的靜態(tài)屬性訪問园匹,比如 Symbol.create
,Symbol.iterator
等等劫灶。要使用它們裸违,可以這樣做:
obj[Symbol.iterator] = function(){ /*..*/ };
要定義你自己的 Symbol,使用 Symbol(..)
原生類型本昏。Symbol(..)
原生類型“構(gòu)造器”很獨(dú)特供汛,因?yàn)樗辉试S你將 new
與它一起使用,這么做會拋出一個錯誤涌穆。
var mysym = Symbol( "my own symbol" );
mysym; // Symbol(my own symbol)
mysym.toString(); // "Symbol(my own symbol)"
typeof mysym; // "symbol"
var a = { };
a[mysym] = "foobar";
Object.getOwnPropertySymbols( a );
// [ Symbol(my own symbol) ]
雖然 Symbol 實(shí)際上不是私有的(在對象上使用 Object.getOwnPropertySymbols(..)
反射怔昨,揭示了 Symbol 其實(shí)是相當(dāng)公開的),但是它們的主要用途可能是私有屬性宿稀,或者類似的特殊屬性趁舀。對于大多數(shù)開發(fā)者,他們也許會在屬性名上加入 _
下劃線前綴祝沸,這在經(jīng)常在慣例上表示:“這是一個私有的/特殊的/內(nèi)部的屬性矮烹,別碰巡蘸!”
注意: Symbol
不是 object
,它們是簡單的基本標(biāo)量擂送。
原生類型原型
每一個內(nèi)建的原生構(gòu)造器都擁有它自己的 .prototype
對象 —— Array.prototype
悦荒,String.prototype
等等。
對于它們特定的對象子類型嘹吨,這些對象含有獨(dú)特的行為搬味。
例如,所有的字符串對象蟀拷,和 string
基本值的擴(kuò)展(通過封箱)碰纬,都可以訪問在 String.prototype
對象上做為方法定義的默認(rèn)行為。
注意: 做為文檔慣例问芬,String.prototype.XYZ
會被縮寫為 String#XYZ
悦析,對于其它所有 .prototype
的屬性都是如此。
-
String#indexOf(..)
:在一個字符串中找出一個子串的位置 -
String#charAt(..)
:訪問一個字符串中某個位置的字符 -
String#substr(..)
此衅、String#substring(..)
和String#slice(..)
:將字符串的一部分抽取為一個新字符串 -
String#toUpperCase()
和String#toLowerCase()
:創(chuàng)建一個轉(zhuǎn)換為大寫或小寫的新字符串 -
String#trim()
:創(chuàng)建一個截去開頭或結(jié)尾空格的新字符串强戴。
這些方法中沒有一個是在 原地 修改字符串的。修改(比如大小寫變換或去空格)會根據(jù)當(dāng)前的值來創(chuàng)建一個新的值挡鞍。
有賴于原型委托(見本系列的 this 與對象原型)骑歹,任何字符串值都可以訪問這些方法:
var a = " abc ";
a.indexOf( "c" ); // 3
a.toUpperCase(); // " ABC "
a.trim(); // "abc"
其他構(gòu)造器的原型包含適用于它們類型的行為,比如 Number#toFixed(..)
(將一個數(shù)字轉(zhuǎn)換為一個固定小數(shù)位的字符串)和 Array#concat(..)
(混合數(shù)組)墨微。所有這些函數(shù)都可以訪問 apply(..)
道媚、call(..)
和 bind(..)
,因?yàn)?Function.prototype
定義了它們翘县。
但是最域,一些原生類型的原型不 僅僅 是單純的對象:
typeof Function.prototype; // "function"
Function.prototype(); // 它是一個空函數(shù)!
RegExp.prototype.toString(); // "/(?:)/" —— 空的正則表達(dá)式
"abc".match( RegExp.prototype ); // [""]
一個特別差勁兒的主意是锈麸,你甚至可以修改這些原生類型的原型(不僅僅是你可能熟悉的添加屬性):
Array.isArray( Array.prototype ); // true
Array.prototype.push( 1, 2, 3 ); // 3
Array.prototype; // [1,2,3]
// 別這么留著它镀脂,要不就等著怪事發(fā)生吧!
// 將`Array.prototype`重置為空
Array.prototype.length = 0;
如你所見掐隐,Function.prototype
是一個函數(shù)狗热,RegExp.prototype
是一個正則表達(dá)式,而 Array.prototype
是一個數(shù)組虑省。有趣吧匿刮?酷吧?
原型作為默認(rèn)值
Function.prototype
是一個空函數(shù)探颈,RegExp.prototype
是一個“空”正則表達(dá)式(也就是不匹配任何東西)熟丸,而 Array.prototype
是一個空數(shù)組,這使它們成了可以賦值給變量的伪节,很好的“默認(rèn)”值 —— 如果這些類型的變量還沒有值光羞。
例如:
function isThisCool(vals,fn,rx) {
vals = vals || Array.prototype;
fn = fn || Function.prototype;
rx = rx || RegExp.prototype;
return rx.test(
vals.map( fn ).join( "" )
);
}
isThisCool(); // true
isThisCool(
["a","b","c"],
function(v){ return v.toUpperCase(); },
/D/
); // false
注意: 在 ES6 中绩鸣,我們不再需要使用 vals = vals || ..
這樣的默認(rèn)值語法技巧了(見第四章),因?yàn)樵诤瘮?shù)聲明中可以通過原生語法為參數(shù)設(shè)定默認(rèn)值(見第五章)纱兑。
這個方式的一個微小的副作用是呀闻,.prototype
已經(jīng)被創(chuàng)建了,而且是內(nèi)建的潜慎,因此它僅被創(chuàng)建 一次捡多。相比之下,使用 []
铐炫、function(){}
和 /(?:)/
這些值本身作為默認(rèn)值垒手,將會(很可能,要看引擎如何實(shí)現(xiàn))在每次調(diào)用 isThisCool(..)
時重新創(chuàng)建這些值(而且稍可能要回收它們)倒信。這可能會消耗內(nèi)存/CPU科贬。
另外,要非常小心不要對 后續(xù)要被修改的值 使用 Array.prototype
做為默認(rèn)值鳖悠。在這個例子中榜掌,vals
是只讀的,但如果你要在原地對 vals
進(jìn)行修改竞穷,那你實(shí)際上修改的是 Array.prototype
本身唐责,這將把你引到剛才提到的坑里!
注意: 雖然我們指出了這些原生類型的原型和一些用處瘾带,但是依賴它們的時候要小心,更要小心以任何形式修改它們熟菲。更多的討論見附錄A“原生原型”看政。
復(fù)習(xí)
JavaScript 為基本類型提供了對象包裝器,被稱為原生類型(String
抄罕、Number
允蚣、Boolean
等等)。這些對象包裝器使這些值可以訪問每種對象子類型的恰當(dāng)行為(String#trim()
和 Array#concat(..)
)呆贿。
如果你有一個像 "abc"
這樣的簡單基本類型標(biāo)量嚷兔,而且你想要訪問它的 length
屬性或某些 String.prototype
方法,JS 會自動地“封箱”這個值(用它所對應(yīng)種類的對象包裝器把它包起來)做入,以滿足這樣的屬性/方法訪問冒晰。