感謝社區(qū)中各位的大力支持,譯者再次奉上一點(diǎn)點(diǎn)福利:阿里云產(chǎn)品券,享受所有官網(wǎng)優(yōu)惠,并抽取幸運(yùn)大獎(jiǎng):點(diǎn)擊這里領(lǐng)取
在第一和第二章中,我們幾次提到了各種內(nèi)建類型,通常稱為“原生類型”,比如String
和Number
。現(xiàn)在讓我們來(lái)仔細(xì)檢視它們。
這是最常用的原生類型的一覽:
String()
Number()
Boolean()
Array()
Object()
Function()
RegExp()
Date()
Error()
-
Symbol()
—— 在ES6中被加入的!
如你所見(jiàn),這些原生類型實(shí)際上是內(nèi)建函數(shù)。
如果你擁有像Java語(yǔ)言那樣的背景,JavaScript的String()
看起來(lái)像是你曾經(jīng)用來(lái)創(chuàng)建字符串值的String(..)
構(gòu)造器。所以,你很快就會(huì)觀察到你可以做這樣的事情:
var s = new String( "Hello World!" );
console.log( s.toString() ); // "Hello World!"
這些原生類型的每一種確實(shí)可以被用作一個(gè)原生類型的構(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é)果是一個(gè)基本類型值("abc"
)的包裝器對(duì)象。
重要的是,typeof
顯示這些對(duì)象不是它們自己的特殊 類型,而是object
類型的子類型。
這個(gè)包裝器對(duì)象可以被進(jìn)一步觀察,像這樣:
console.log( a );
這個(gè)語(yǔ)句的輸出會(huì)根據(jù)你使用的瀏覽器變化,因?yàn)閷?duì)于開(kāi)發(fā)者的查看,開(kāi)發(fā)者控制臺(tái)可以自由選擇它認(rèn)為合適的方式來(lái)序列化對(duì)象。
注意: 在寫(xiě)作本書(shū)時(shí),最新版的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)擊它可以打開(kāi)對(duì)象查看器。當(dāng)然,這些結(jié)果是總頻繁變更的,而且你的體驗(yàn)也許不同。
重點(diǎn)是,new String("abc")
為"abc"
創(chuàng)建了一個(gè)字符串包裝器對(duì)象,而不僅是基本類型值"abc"
本身。
內(nèi)部[[Class]]
typeof
的結(jié)果為"object"
的值(比如數(shù)組)被額外地打上了一個(gè)內(nèi)部的標(biāo)簽屬性[[Class]]
(請(qǐng)把它考慮為一個(gè)內(nèi)部的分類方法,而非與傳統(tǒng)的面向?qū)ο缶幋a的類有關(guān))。這個(gè)屬性不能直接地被訪問(wèn),但通常可以間接地通過(guò)在這個(gè)值上借用默認(rèn)的Object.prototype.toString(..)
方法調(diào)用來(lái)展示。舉例來(lái)說(shuō):
Object.prototype.toString.call( [1,2,3] ); // "[object Array]"
Object.prototype.toString.call( /regex-literal/i ); // "[object RegExp]"
所以,對(duì)于這個(gè)例子中的數(shù)組來(lái)說(shuō),內(nèi)部的[[Class]]
值是"Array"
,而對(duì)于正則表達(dá)式,它是"RegExp"
。在大多數(shù)情況下,這個(gè)內(nèi)部的[[Class]]
值對(duì)應(yīng)于關(guān)聯(lián)這個(gè)值的內(nèi)建的原生類型構(gòu)造器(見(jiàn)下面的討論),但事實(shí)卻不總是這樣。
基本類型呢?首先,null
和undefined
:
Object.prototype.toString.call( null ); // "[object Null]"
Object.prototype.toString.call( undefined ); // "[object Undefined]"
你會(huì)注意到,不存在Null()
和Undefined()
原生類型構(gòu)造器,但不管怎樣"Null"
和"Undefined"
是被暴露出來(lái)的內(nèi)部[[Class]]
值。
但是對(duì)于像string
,number
,和boolean
這樣的簡(jiǎn)單基本類型,實(shí)際上會(huì)啟動(dòng)另一種行為,通常稱為“封箱(boxing)”(見(jiàn)下一節(jié)“封箱包裝器”):
Object.prototype.toString.call( "abc" ); // "[object String]"
Object.prototype.toString.call( 42 ); // "[object Number]"
Object.prototype.toString.call( true ); // "[object Boolean]"
在這個(gè)代碼段中,每一個(gè)簡(jiǎn)單基本類型都自動(dòng)地被它們分別對(duì)應(yīng)的對(duì)象包裝器封箱,這就是為什么"String"
,"Number"
,和"Boolean"
分別被顯示為內(nèi)部[[Class]]
值。
注意: 從ES5發(fā)展到ES6的過(guò)程中,這里展示的toString()
和[[Class]]
的行為發(fā)生了一點(diǎn)兒改變,但我們會(huì)在本系列的 ES6與未來(lái) 一書(shū)中講解它們的細(xì)節(jié)。
封箱包裝器
這些對(duì)象包裝器服務(wù)于一個(gè)非常重要的目的。基本類型值沒(méi)有屬性或方法,所以為了訪問(wèn).length
或.toString()
你需要這個(gè)值的對(duì)象包裝器。值得慶幸的是,JS將會(huì)自動(dòng)地 封箱(也就是包裝)基本類型值來(lái)滿足這樣的訪問(wèn)。
var a = "abc";
a.length; // 3
a.toUpperCase(); // "ABC"
那么,如果你想以通常的方式訪問(wèn)這些字符串值上的屬性/方法,比如一個(gè)for
循環(huán)的i < a.length
條件,這么做看起來(lái)很有道理:一開(kāi)始就得到一個(gè)這個(gè)值的對(duì)象形式,于是JS引擎就不需要隱含地為你創(chuàng)建一個(gè)。
但事實(shí)證明這是一個(gè)壞主意。瀏覽器們長(zhǎng)久以來(lái)就對(duì).length
這樣的常見(jiàn)情況進(jìn)行性能優(yōu)化,這意味著如果你試著直接使用對(duì)象形式(它們沒(méi)有被優(yōu)化過(guò))進(jìn)行“提前優(yōu)化”,那么實(shí)際上你的程序?qū)?huì) 變慢。
一般來(lái)說(shuō),基本上沒(méi)有理由直接使用對(duì)象形式。讓封箱在需要的地方隱含地發(fā)生會(huì)更好。換句話說(shuō),永遠(yuǎn)也不要做new String("abc")
,new Number(42)
這樣的事情——應(yīng)當(dāng)總是偏向于使用基本類型字面量"abc"
和42
。
對(duì)象包裝器的坑
如果你 確實(shí) 選擇要直接使用對(duì)象包裝器,那么有幾個(gè)坑你應(yīng)該注意。
舉個(gè)例子,考慮Boolean
包裝的值:
var a = new Boolean( false );
if (!a) {
console.log( "Oops" ); // 永遠(yuǎn)不會(huì)運(yùn)行
}
這里的問(wèn)題是,雖然你為值false
創(chuàng)建了一個(gè)對(duì)象包裝器,但是對(duì)象本身是“truthy”(見(jiàn)第四章),所以使用對(duì)象的效果是與使用底層的值false
本身相反的,這與通常的期望十分不同。
如果你想手動(dòng)封箱一個(gè)基本類型值,你可以使用Object(..)
函數(shù)(沒(méi)有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]"
再說(shuō)一遍,通常不鼓勵(lì)直接使用封箱的包裝器對(duì)象(比如上面的b
和c
),但你可能會(huì)遇到一些它們有用的罕見(jiàn)情況。
開(kāi)箱
如果你有一個(gè)包裝器對(duì)象,而你想要取出底層的基本類型值,你可以使用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)以一種查詢基本類型值的方式使用對(duì)象包裝器時(shí),開(kāi)箱也會(huì)隱含地發(fā)生。這個(gè)處理的過(guò)程(強(qiáng)制轉(zhuǎn)換)將會(huì)在第四章中更詳細(xì)地講解,但簡(jiǎn)單地說(shuō):
var a = new String( "abc" );
var b = a + ""; // `b` 擁有開(kāi)箱后的基本類型值"abc"
typeof a; // "object"
typeof b; // "string"
原生類型作為構(gòu)造器
對(duì)于array
,object
,function
,和正則表達(dá)式值來(lái)說(shuō),使用字面形式來(lái)創(chuàng)建它們的值幾乎總是更好的選擇,而且字面形式與構(gòu)造器形式所創(chuàng)建的值是同一種對(duì)象(也就是,沒(méi)有非包裝的值)。
正如我們剛剛在上面看到的其他原生類型,除非你真的知道你需要這些構(gòu)造器形式,一般來(lái)說(shuō)應(yīng)當(dāng)避免使用它們,這主要是因?yàn)樗鼈儠?huì)帶來(lái)一些你可能不會(huì)想要對(duì)付的異常和陷阱。
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)鍵字。如果你省略它,它也會(huì)像你已經(jīng)使用了一樣動(dòng)作。所以Array(1,2,3)
和new Array(1,2,3)
的結(jié)果是一樣的。
Array
構(gòu)造器有一種特殊形式,如果它僅僅被傳入一個(gè)number
參數(shù),與將這個(gè)值作為數(shù)組的 內(nèi)容 不同,它會(huì)被認(rèn)為是用來(lái)“預(yù)定數(shù)組大小”(嗯,某種意義上)用的長(zhǎng)度。
這是個(gè)可怕的主意。首先,你會(huì)意外地用錯(cuò)這種形式,因?yàn)樗苋菀淄洝?/p>
但更重要的是,其實(shí)沒(méi)有預(yù)定數(shù)組大小這樣的東西。你所創(chuàng)建的是一個(gè)空數(shù)組,并將這個(gè)數(shù)組的length
屬性設(shè)置為那個(gè)指定的數(shù)字值。
一個(gè)數(shù)組在它的值槽上沒(méi)有明確的值,但是有一個(gè)length
屬性意味著這些值槽是存在的,在JS中這是一個(gè)詭異的數(shù)據(jù)結(jié)構(gòu),它帶有一些非常奇怪且令人困惑的行為。可以創(chuàng)建這樣的值的能力,完全源自于老舊的,已經(jīng)廢棄的,僅具有歷史意義的功能(比如arguments
這樣的“類數(shù)組對(duì)象”)。
注意: 帶有至少一個(gè)“空值槽”的數(shù)組經(jīng)常被稱為“稀散數(shù)組”。
這是另外一個(gè)例子,展示瀏覽器的開(kāi)發(fā)者控制臺(tái)在如何表示這樣的對(duì)象上有所不同,它產(chǎn)生了更多的困惑。
舉例來(lái)說(shuō):
var a = new Array( 3 );
a.length; // 3
a;
在Chrome中a
的序列化表達(dá)是(在本書(shū)寫(xiě)作時(shí)):[ undefined x 3 ]
。這真的很不幸。 它暗示著在這個(gè)數(shù)組的值槽中有三個(gè)undefined
值,而事實(shí)上這樣的值槽是不存在的(所謂的“空值槽(empty slots)”——也是一個(gè)爛名字!)。
要觀察這種不同,試試這段代碼:
var a = new Array( 3 );
var b = [ undefined, undefined, undefined ];
var c = [];
c.length = 3;
a;
b;
c;
注意: 正如你在這個(gè)例子中看到的c
,數(shù)組中的空值槽可以在數(shù)組的創(chuàng)建之后發(fā)生。將數(shù)組的length
改變?yōu)槌^(guò)它實(shí)際定義的槽值的數(shù)目,你就隱含地引入了空值槽。事實(shí)上,你甚至可以在上面的代碼段中調(diào)用delete b[1]
,而這么做將會(huì)在b
的中間引入一個(gè)空值槽。
對(duì)于b
(在當(dāng)前的Chrome中),你會(huì)發(fā)現(xiàn)它的序列化表現(xiàn)為[ undefined, undefined, undefined ]
,與之相對(duì)的是a
和c
的[ undefined x 3 ]
。糊涂了吧?是的,大家都糊涂了。
更糟糕的是,在寫(xiě)作本書(shū)時(shí),F(xiàn)irefox對(duì)a
和c
報(bào)告[ , , , ]
。你發(fā)現(xiàn)為什么這使人犯糊涂了嗎?仔細(xì)看。三個(gè)逗號(hào)表示有四個(gè)值槽,不是我們期望的三個(gè)值槽。
什么!? Firefox在它們的序列化表達(dá)的末尾放了一個(gè)額外的,
,因?yàn)樵贓S5中,列表(數(shù)組值,屬性列表等等)末尾的逗號(hào)是允許的(被砍掉并忽略)。所以如果你在你的程序或控制臺(tái)中敲入[ , , , ]
值,你實(shí)際上得到的是一個(gè)底層為[ , , ]
的值(也就是,一個(gè)帶有三個(gè)空值槽的數(shù)組)。這種選擇,雖然在閱讀開(kāi)發(fā)者控制臺(tái)時(shí)使人困惑,但是因?yàn)樗箍截愓迟N的時(shí)候準(zhǔn)確,所以被留了下來(lái)。
如果你現(xiàn)在在搖頭或翻白眼兒,你并不孤單!(聳肩)
不幸的是,事情越來(lái)越糟。比在控制臺(tái)的輸出產(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)用會(huì) 失敗 是因?yàn)橹挡鄹揪筒粚?shí)際存在,所以map(..)
沒(méi)有東西可以迭代。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, "-" ); // "--"
如你所見(jiàn),join(..)
好用僅僅是因?yàn)樗?認(rèn)為 值槽存在,并循環(huán)至length
值。不管map(..)
內(nèi)部是在做什么,它(顯然)沒(méi)有做出這樣的假設(shè),所以源自于奇怪的“空值槽”數(shù)組的結(jié)果出人意料,而且好像是失敗了。
那么,如果你想要 確實(shí) 創(chuàng)建一個(gè)實(shí)際的undefined
值的數(shù)組(不只是“空值槽”),你如何才能做到呢(除了手動(dòng)以外)?
var a = Array.apply( null, { length: 3 } );
a; // [ undefined, undefined, undefined ]
糊涂了吧?是的。這里是它大概的工作方式。
apply(..)
是一個(gè)對(duì)所有函數(shù)可用的工具方法,它以一種特殊方式調(diào)用這個(gè)使用它的函數(shù)。
第一個(gè)參數(shù)是一個(gè)this
對(duì)象綁定(在本系列的 this與對(duì)象原型 中有詳細(xì)講解),在這里我們不關(guān)心它,所以我們將它設(shè)置為null
。第二個(gè)參數(shù)應(yīng)該是一個(gè)數(shù)組(或 像 數(shù)組的東西——也就是“類數(shù)組對(duì)象”)。這個(gè)“數(shù)組”的內(nèi)容作為這個(gè)函數(shù)的參數(shù)“擴(kuò)散”開(kāi)來(lái)。
所以,Array.apply(..)
在調(diào)用Array(..)
函數(shù),并將一個(gè)值({ length: 3 }
對(duì)象值)作為它的參數(shù)值分散開(kāi)。
在apply(..)
內(nèi)部,我們可以預(yù)見(jiàn)這里有另一個(gè)for
循環(huán)(有些像上面的join(..)
),它從0
開(kāi)始上升但不包含至length
(這個(gè)例子中是3
)。
對(duì)于每一個(gè)索引,它從對(duì)象中取得相應(yīng)的鍵。所以如果這個(gè)數(shù)組對(duì)象參數(shù)在apply(..)
內(nèi)部被命名為arr
,那么這種屬性訪問(wèn)實(shí)質(zhì)上是arr[0]
,arr[1]
,和arr[2]
。當(dāng)然,沒(méi)有一個(gè)屬性是在{ length: 3 }
對(duì)象值上存在的,所以這三個(gè)屬性訪問(wèn)都將返回值undefined
。
換句話說(shuō),調(diào)用Array(..)
的結(jié)局基本上是這樣:Array(undefined,undefined,undefined)
,這就是我們?nèi)绾蔚玫揭粋€(gè)填滿undefined
值的數(shù)組的,而非僅僅是一些(瘋狂的)空值槽。
雖然對(duì)于創(chuàng)建一個(gè)填滿undefined
值的數(shù)組來(lái)說(shuō),Array.apply( null, { length: 3 } )
是一個(gè)奇怪而且繁冗的方法,但是它要比使用砸自己的腳似的Array(3)
空值槽要可靠和好得 太多了。
底線:你 在任何情況下,永遠(yuǎn)不,也不應(yīng)該有意地創(chuàng)建并使用詭異的空值槽數(shù)組。就別這么干。它們是怪胎。
Object(..)
,Function(..)
,和RegExp(..)
Object(..)
/Function(..)
/RegExp(..)
構(gòu)造器一般來(lái)說(shuō)也是可選的(因此除非是特別的目的,應(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;
幾乎沒(méi)有理由使用new Object()
構(gòu)造器形式,尤其因?yàn)樗鼜?qiáng)迫你一個(gè)一個(gè)地添加屬性,而不是像對(duì)象的字面形式那樣一次添加許多。
Function
構(gòu)造器僅在最最罕見(jiàn)的情況下有用,也就是你需要?jiǎng)討B(tài)地定義一個(gè)函數(shù)的參數(shù)和/或它的函數(shù)體。不要將Function(..)
僅僅作為另一種形式的eval(..)
。你幾乎永遠(yuǎn)不會(huì)需要用這種方式動(dòng)態(tài)定義一個(gè)函數(shù)。
用字面量形式(/^a*b+/g
)定義正則表達(dá)式是被大力采用的,不僅因?yàn)檎Z(yǔ)法簡(jiǎn)單,而且還有性能的原因——JS引擎會(huì)在代碼執(zhí)行前預(yù)編譯并緩存它們。和我們迄今看到的其他構(gòu)造器形式不同,RegExp(..)
有一些合理的用途:用來(lái)動(dòng)態(tài)定義一個(gè)正則表達(dá)式的范例。
var name = "Kyle";
var namePattern = new RegExp( "\\b(?:" + name + ")+\\b", "ig" );
var matches = someText.match( namePattern );
這樣的場(chǎng)景在JS程序中一次又一次地合法出現(xiàn),所以你有需要使用new RegExp("pattern","flags")
形式。
Date(..)
和Error(..)
Date(..)
和Error(..)
原生類型構(gòu)造器要比其他種類的原生類型有用得多,因?yàn)樗鼈儧](méi)有字面量形式。
要?jiǎng)?chuàng)建一個(gè)日期對(duì)象值,你必須使用new Date()
。Date(..)
構(gòu)造器接收可選參數(shù)值來(lái)指定要使用的日期/時(shí)間,但是如果省略的話,就會(huì)使用當(dāng)前的日期/時(shí)間。
目前你構(gòu)建一個(gè)日期對(duì)象的最常見(jiàn)的理由是要得到當(dāng)前的時(shí)間戳(一個(gè)有符號(hào)整數(shù),從1970年1月1日開(kāi)始算起的毫秒數(shù))。你可以在一個(gè)日期對(duì)象實(shí)例上調(diào)用getTime()
得到它。
但是在ES5中,一個(gè)更簡(jiǎn)單的方法是調(diào)用定義為Date.now()
的靜態(tài)幫助函數(shù)。而且在前ES5中填補(bǔ)它很容易:
if (!Date.now) {
Date.now = function(){
return (new Date()).getTime();
};
}
注意: 如果你不帶new
調(diào)用Date()
,你將會(huì)得到一個(gè)那個(gè)時(shí)刻的日期/時(shí)間的字符串表達(dá)。在語(yǔ)言規(guī)范中沒(méi)有規(guī)定這個(gè)表達(dá)的確切形式,雖然各個(gè)瀏覽器趨向于贊同使用這樣的東西:"Fri Jul 18 2014 00:31:02 GMT-0500 (CDT)"
。
Error(..)
構(gòu)造器(很像上面的Array()
)在有new
與沒(méi)有new
時(shí)的行為是相同的。
你想要?jiǎng)?chuàng)建error對(duì)象的主要原因是,它會(huì)將當(dāng)前的執(zhí)行棧上下文捕捉進(jìn)對(duì)象中(在大多數(shù)JS引擎中,在創(chuàng)建后使用只讀的.stack
屬性表示)。這個(gè)棧上下文包含函數(shù)調(diào)用棧和error對(duì)象被創(chuàng)建時(shí)的行號(hào),這使調(diào)試這個(gè)錯(cuò)誤更簡(jiǎn)單。
典型地,你將與throw
操作符一起使用這樣的error對(duì)象:
function foo(x) {
if (!x) {
throw new Error( "x wasn't provided" );
}
// ..
}
Error對(duì)象實(shí)例一般擁有至少一個(gè)message
屬性,有時(shí)還有其他屬性(你應(yīng)當(dāng)將它們作為只讀的),比如type
。然而,與其檢視上面提到的stack
屬性,最好是在error對(duì)象上調(diào)用toString()
(明確地調(diào)用,或者是通過(guò)強(qiáng)制轉(zhuǎn)換隱含地調(diào)用——見(jiàn)第四章)來(lái)得到一個(gè)格式友好的錯(cuò)誤消息。
提示: 技術(shù)上講,除了一般的Error(..)
原生類型以外,還有幾種特定錯(cuò)誤的原生類型:EvalError(..)
,RangeError(..)
,ReferenceError(..)
,SyntaxError(..)
, TypeError(..)
,和URIError(..)
。但是手動(dòng)使用這些特定錯(cuò)誤原生類型十分少見(jiàn)。如果你的程序確實(shí)遭受了一個(gè)真實(shí)的異常,它們是會(huì)自動(dòng)地被使用的(比如引用一個(gè)未聲明的變量而得到一個(gè)ReferenceError
錯(cuò)誤)。
Symbol(..)
在ES6中,新增了一個(gè)基本值類型,稱為“Symbol(標(biāo)志)”。Symbol是一種特殊的“獨(dú)一無(wú)二”(不是嚴(yán)格保證的!)的值,可以作為對(duì)象上的屬性使用而幾乎不必?fù)?dān)心任何沖突。它們主要是為特殊的ES6結(jié)構(gòu)的內(nèi)建行為設(shè)計(jì)的,但你也可以定義你自己的symbol。
Symbol可以用做屬性名,但是你不能從你的程序中看到或訪問(wèn)一個(gè)symbol的實(shí)際值,從開(kāi)發(fā)者控制臺(tái)也不行。例如,如果你在開(kāi)發(fā)者控制臺(tái)中對(duì)一個(gè)Symbol求值,將會(huì)顯示Symbol(Symbol.create)
之類的東西。
在ES6中有幾種預(yù)定義的Symbol,做為Symbol
函數(shù)對(duì)象的靜態(tài)屬性訪問(wèn),比如Symbol.create
,Symbol.iterator
等等。要使用它們,可以這樣做:
obj[Symbol.iterator] = function(){ /*..*/ };
要定義你自己的Symbol,使用Symbol(..)
原生類型。Symbol(..)
原生類型“構(gòu)造器”很獨(dú)特,因?yàn)樗辉试S你將new
與它一起使用,這么做會(huì)拋出一個(gè)錯(cuò)誤。
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í)際上不是私有的(在對(duì)象上使用Object.getOwnPropertySymbols(..)
反射,揭示了Symbol其實(shí)是相當(dāng)公開(kāi)的),但是它們的主要用途可能是私有屬性,或者類似的特殊屬性。對(duì)于大多數(shù)開(kāi)發(fā)者,他們也許會(huì)在屬性名上加入_
下劃線前綴,這在經(jīng)常在慣例上表示:“這是一個(gè)私有的/特殊的/內(nèi)部的屬性,別碰!”
注意: Symbol
不是 object
,它們是簡(jiǎn)單的基本標(biāo)量。
原生類型原型
每一個(gè)內(nèi)建的原生構(gòu)造器都擁有它自己的.prototype
對(duì)象——Array.prototype
,String.prototype
等等。
對(duì)于它們特定的對(duì)象子類型,這些對(duì)象含有獨(dú)特的行為。
例如,所有的字符串對(duì)象,和string
基本值的擴(kuò)展(通過(guò)封箱),都可以訪問(wèn)在String.prototype
對(duì)象上做為方法定義的默認(rèn)行為。
注意: 做為文檔慣例,String.prototype.XYZ
會(huì)被縮寫(xiě)為String#XYZ
,對(duì)于其它所有.prototype
的屬性都是如此。
-
String#indexOf(..)
:在一個(gè)字符串中找出一個(gè)子串的位置 -
String#charAt(..)
:訪問(wèn)一個(gè)字符串中某個(gè)位置的字符 -
String#substr(..)
,String#substring(..)
,和String#slice(..)
:將字符串的一部分抽取為一個(gè)新字符串 -
String#toUpperCase()
和String#toLowerCase()
:創(chuàng)建一個(gè)轉(zhuǎn)換為大寫(xiě)或小寫(xiě)的新字符串 -
String#trim()
:創(chuàng)建一個(gè)截去開(kāi)頭或結(jié)尾空格的新字符串。
這些方法中沒(méi)有一個(gè)是在 原地 修改字符串的。修改(比如大小寫(xiě)變換或去空格)會(huì)根據(jù)當(dāng)前的值來(lái)創(chuàng)建一個(gè)新的值。
有賴于原型委托(見(jiàn)本系列的 this與對(duì)象原型),任何字符串值都可以訪問(wèn)這些方法:
var a = " abc ";
a.indexOf( "c" ); // 3
a.toUpperCase(); // " ABC "
a.trim(); // "abc"
其他構(gòu)造器的原型包含適用于它們類型的行為,比如Number#toFixed(..)
(將一個(gè)數(shù)字轉(zhuǎn)換為一個(gè)固定小數(shù)位的字符串)和Array#concat(..)
(混合數(shù)組)。所有這些函數(shù)都可以訪問(wèn)apply(..)
,call(..)
,和bind(..)
,因?yàn)?code>Function.prototype定義了它們。
但是,一些原生類型的原型不 僅僅 是單純的對(duì)象:
typeof Function.prototype; // "function"
Function.prototype(); // 它是一個(gè)空函數(shù)!
RegExp.prototype.toString(); // "/(?:)/" —— 空的正則表達(dá)式
"abc".match( RegExp.prototype ); // [""]
一個(gè)特別差勁兒的主意是,你甚至可以修改這些原生類型的原型(不僅僅是你可能熟悉的添加屬性):
Array.isArray( Array.prototype ); // true
Array.prototype.push( 1, 2, 3 ); // 3
Array.prototype; // [1,2,3]
// 別這么留著它,要不就等著怪事發(fā)生吧!
// 將`Array.prototype`重置為空
Array.prototype.length = 0;
如你所見(jiàn),Function.prototype
是一個(gè)函數(shù),RegExp.prototype
是一個(gè)正則表達(dá)式,而Array.prototype
是一個(gè)數(shù)組。有趣吧?酷吧?
原型作為默認(rèn)值
Function.prototype
是一個(gè)空函數(shù),RegExp.prototype
是一個(gè)“空”正則表達(dá)式(也就是不匹配任何東西),而Array.prototype
是一個(gè)空數(shù)組,這使它們成了可以賦值給變量的,很好的“默認(rèn)”值——如果這些類型的變量還沒(méi)有值。
例如:
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ǔ)法技巧了(見(jiàn)第四章),因?yàn)樵诤瘮?shù)聲明中可以通過(guò)原生語(yǔ)法為參數(shù)設(shè)定默認(rèn)值(見(jiàn)第五章)。
這個(gè)方式的一個(gè)微小的副作用是,.prototype
已經(jīng)被創(chuàng)建了,而且是內(nèi)建的,因此它僅被創(chuàng)建 一次。相比之下,使用[]
,function(){}
,和/(?:)/
這些值本身作為默認(rèn)值,將會(huì)(很可能,要看引擎如何實(shí)現(xiàn))在每次調(diào)用isThisCool(..)
時(shí)重新創(chuàng)建這些值(而且稍可能要回收它們)。這可能會(huì)消耗內(nèi)存/CPU。
另外,要非常小心不要對(duì) 后續(xù)要被修改的值 使用Array.prototype
做為默認(rèn)值。在這個(gè)例子中,vals
是只讀的,但如果你要在原地對(duì)vals
進(jìn)行修改,那你實(shí)際上修改的是Array.prototype
本身,這將把你引到剛才提到的坑里!
注意: 雖然我們指出了這些原生類型的原型和一些用處,但是依賴它們的時(shí)候要小心,更要小心以任何形式修改它們。更多的討論見(jiàn)附錄A“原生原型”。
復(fù)習(xí)
JavaScript為基本類型提供了對(duì)象包裝器,被稱為原生類型(String
,Number
,Boolean
,等等)。這些對(duì)象包裝器使這些值可以訪問(wèn)每種對(duì)象子類型的恰當(dāng)行為(String#trim()
和Array#concat(..)
)。
如果你有一個(gè)像"abc"
這樣的簡(jiǎn)答基本類型標(biāo)量,而且你想要訪問(wèn)它的length
屬性或某些String.prototype
方法,JS會(huì)自動(dòng)地“封箱”這個(gè)值(用它所對(duì)應(yīng)種類的對(duì)象包裝器把它包起來(lái)),以滿足這樣的屬性/方法訪問(wèn)。