第三章:原生類型

特別說明,為便于查閱坑鱼,文章轉(zhuǎn)自https://github.com/getify/You-Dont-Know-JS

在第一和第二章中枷遂,我們幾次提到了各種內(nèi)建類型,通常稱為“原生類型”创译,比如 StringNumber。現(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í)卻不總是這樣对省。

基本類型呢蝗拿?首先,nullundefined

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]"

再說一遍,通常不鼓勵直接使用封箱的包裝器對象(比如上面的 bc)赵抢,但你可能會遇到一些它們有用的罕見情況剧蹂。

開箱

如果你有一個包裝器對象,而你想要取出底層的基本類型值烦却,你可以使用 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冒冬、objectfunction 和正則表達(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 ],與之相對的是 ac[ undefined x 3 ]描沟。糊涂了吧飒泻?是的,大家都糊涂了吏廉。

更糟糕的是泞遗,在寫作本書時,F(xiàn)irefox 對 ac 報告 [ , , , ]席覆。你發(fā)現(xiàn)為什么這使人犯糊涂了嗎史辙?仔細(xì)看。三個逗號表示有四個值槽,不是我們期望的三個值槽聊倔。

什么;薇小? Firefox 在它們的序列化表達(dá)的末尾放了一個額外的 ,耙蔑,因?yàn)樵?ES5 中见妒,列表(數(shù)組值,屬性列表等等)末尾的逗號是允許的(被砍掉并忽略)甸陌。所以如果你在你的程序或控制臺中敲入 [ , , , ] 值须揣,你實(shí)際上得到的是一個底層為 [ , , ] 的值(也就是,一個帶有三個空值槽的數(shù)組)钱豁。這種選擇耻卡,雖然在閱讀開發(fā)者控制臺時使人困惑,但是因?yàn)樗箍截愓迟N的時候準(zhǔn)確牲尺,所以被留了下來卵酪。

如果你現(xiàn)在在搖頭或翻白眼兒,你并不孤單=招弧(聳肩)

不幸的是凛澎,事情越來越糟。比在控制臺的輸出產(chǎn)生的困惑更糟的是估蹄,上面代碼段中的 ab 實(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.createSymbol.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)種類的對象包裝器把它包起來)做入,以滿足這樣的屬性/方法訪問冒晰。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市竟块,隨后出現(xiàn)的幾起案子壶运,更是在濱河造成了極大的恐慌,老刑警劉巖浪秘,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蒋情,死亡現(xiàn)場離奇詭異埠况,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)棵癣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進(jìn)店門辕翰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人狈谊,你說我怎么就攤上這事喜命。” “怎么了的畴?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵渊抄,是天一觀的道長。 經(jīng)常有香客問我丧裁,道長护桦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任煎娇,我火速辦了婚禮二庵,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘缓呛。我一直安慰自己催享,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布哟绊。 她就那樣靜靜地躺著因妙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪票髓。 梳的紋絲不亂的頭發(fā)上攀涵,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天,我揣著相機(jī)與錄音洽沟,去河邊找鬼以故。 笑死,一個胖子當(dāng)著我的面吹牛裆操,可吹牛的內(nèi)容都是我干的怒详。 我是一名探鬼主播,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼踪区,長吁一口氣:“原來是場噩夢啊……” “哼昆烁!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起朽缴,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤善玫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體茅郎,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蜗元,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了系冗。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奕扣。...
    茶點(diǎn)故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖掌敬,靈堂內(nèi)的尸體忽然破棺而出惯豆,到底是詐尸還是另有隱情,我是刑警寧澤奔害,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布楷兽,位于F島的核電站,受9級特大地震影響华临,放射性物質(zhì)發(fā)生泄漏芯杀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一雅潭、第九天 我趴在偏房一處隱蔽的房頂上張望揭厚。 院中可真熱鬧,春花似錦扶供、人聲如沸筛圆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽太援。三九已至,卻和暖如春扳碍,著一層夾襖步出監(jiān)牢的瞬間粉寞,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工左腔, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人捅儒。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓液样,卻偏偏與公主長得像,于是被迫代替她去往敵國和親巧还。 傳聞我的和親對象是個殘疾皇子鞭莽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 官方中文版原文鏈接 感謝社區(qū)中各位的大力支持,譯者再次奉上一點(diǎn)點(diǎn)福利:阿里云產(chǎn)品券麸祷,享受所有官網(wǎng)優(yōu)惠澎怒,并抽取幸運(yùn)大...
    HetfieldJoe閱讀 1,991評論 0 8
  • 國家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 10,989評論 6 13
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)阶牍,斷路器喷面,智...
    卡卡羅2017閱讀 134,665評論 18 139
  • 【阿呆系列第6首】 眾人看球賽星瘾。廣告時間調(diào)到電影頻道 一對男女在爭吵,恨不得殺了對方 時間差不多惧辈,要調(diào)回體育頻道 ...
    文學(xué)山主編山下閱讀 449評論 0 0
  • 也許你也有這樣的情況琳状,看見別人的有一個什么好東西自己也想要,第一感受并非替他感到高興盒齿,我們的思維方式往往是從自己的...
    真真卒跡閱讀 214評論 0 0