你不懂JS:類型與文法 第三章:原生類型

官方中文版原文鏈接

感謝社區(qū)中各位的大力支持,譯者再次奉上一點(diǎn)點(diǎn)福利:阿里云產(chǎn)品券,享受所有官網(wǎng)優(yōu)惠,并抽取幸運(yùn)大獎:點(diǎn)擊這里領(lǐng)取

在第一和第二章中,我們幾次提到了各種內(nèi)建類型,通常稱為“原生類型”,比如StringNumber?,F(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]]值。

但是對于像stringnumber,和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,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 ],與之相對的是ac[ undefined x 3 ]。糊涂了吧?是的,大家都糊涂了。

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

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

如果你現(xiàn)在在搖頭或翻白眼兒,你并不孤單?。柤纾?/p>

不幸的是,事情越來越糟。比在控制臺的輸出產(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ù)值分散開。

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(標(biāo)志)”。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)?code>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為基本類型提供了對象包裝器,被稱為原生類型(StringNumberBoolean,等等)。這些對象包裝器使這些值可以訪問每種對象子類型的恰當(dāng)行為(String#trim()Array#concat(..))。

如果你有一個像"abc"這樣的簡答基本類型標(biāo)量,而且你想要訪問它的length屬性或某些String.prototype方法,JS會自動地“封箱”這個值(用它所對應(yīng)種類的對象包裝器把它包起來),以滿足這樣的屬性/方法訪問。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容