第3章 原生函數

第 3 章原生函數

常用的原生函數有:

  • String()

  • Number()

  • Boolean()

  • Array()

  • Object()

  • Function()

  • RegExp()

  • Date()

  • Error()

  • Symbol()——ES6 中新加入的!

JavaScript 中 的 String() 和 Java 中 的 字 符 串 構 造 函 數
String(..) 非常相似,可以這樣來用:

var s = new String( "Hello World!" );
console.log( s.toString() ); // "Hello World!"

原生函數可以被當作構造函數來使用,但其構造出來的對象可能會和我們設想的有所
出入:

var a = new String( "abc" );
typeof a; // 是"object",不是"String"
a instanceof String; // true
Object.prototype.toString.call( a ); // "[object String]"

通過構造函數(如 new String("abc"))創建出來的是封裝了基本類型值(如 "abc")的封
裝對象。

typeof 在這里返回的是對象類型的子類型。

可以這樣來查看封裝對象:

console.log( a );

在本書寫作期間, Chrome 的最新版本是這樣顯示的: String {0: "a", 1:
"b", 2: "c", length: 3, [[PrimitiveValue]]: "abc"},而老版本這樣顯示:
String {0: "a", 1: "b", 2: "c"}。最新版本的 Firefox 這樣顯示: String
["a","b","c"];老版本這樣顯示: "abc",并且可以點擊打開對象查看器。
這些輸出結果隨著瀏覽器的演進不斷變化,也帶給人們不同的體驗。

再次強調, new String("abc") 創建的是字符串 "abc" 的封裝對象,而非基本類型值 "abc"。

3.1 內部屬性 [[Class]]

所有 typeof 返回值為 "object" 的對象(如數組)都包含一個內部屬性 [[Class]](我們可
以把它看作一個內部的分類,而非傳統的面向對象意義上的類)。這個屬性無法直接訪問,
一般通過 Object.prototype.toString(..) 來查看。

Object.prototype.toString.call( [1,2,3] );
// "[object Array]"
Object.prototype.toString.call( /regex-literal/i );
// "[object RegExp]"

對象的內部 [[Class]] 屬性和創建該對象的內建原生構造函數相對應,但并非
總是如此。

基本類型值

Object.prototype.toString.call( null );
// "[object Null]"
Object.prototype.toString.call( undefined );
// "[object Undefined]"

雖然 Null() 和 Undefined() 這樣的原生構造函數并不存在,但是內部 [[Class]] 屬性值仍
然是 "Null" 和 "Undefined"。

其他基本類型值(如字符串、數字和布爾)的情況有所不同,通常稱為“包裝”

Object.prototype.toString.call( "abc" );
// "[object String]"
Object.prototype.toString.call( 42 );
// "[object Number]"
Object.prototype.toString.call( true );
// "[object Boolean]

上例中基本類型值被各自的封裝對象自動包裝,所以它們的內部 [[Class]] 屬性值分別為
"String"、 "Number" 和 "Boolean"。

從 ES5 到 ES6, toString() 和 [[Class]] 的行為發生了一些變化

3.2 封裝對象包裝

由 于 基 本 類 型 值 沒 有 .length
和 .toString() 這樣的屬性和方法,需要通過封裝對象才能訪問,此時 JavaScript 會自動為
基本類型值包裝( box 或者 wrap)一個封裝對象:

var a = "abc";
a.length; // 3
a.toUpperCase(); // "ABC"

如果需要經常用到這些字符串屬性和方法,比如在 for 循環中使用 i < a.length,那么從
一開始就創建一個封裝對象也許更為方便,這樣 JavaScript 引擎就不用每次都自動創建了。
但實際證明這并不是一個好辦法,因為瀏覽器已經為 .length 這樣的常見情況做了性能優
化,直接使用封裝對象來“提前優化”代碼反而會降低執行效率。
一般情況下,我們不需要直接使用封裝對象。最好的辦法是讓 JavaScript 引擎自己決定什
么時候應該使用封裝對象。換句話說,就是應該優先考慮使用 "abc" 和 42 這樣的基本類型
值,而非 new String("abc") 和 new Number(42)。

封裝對象釋疑

var a = new Boolean( false );
if (!a) {
console.log( "Oops" ); // 執行不到這里
}

我們為 false 創建了一個封裝對象,然而該對象是真值(“ truthy”,即總是返回 true,參見
第 4 章),所以這里使用封裝對象得到的結果和使用 false 截然相反。

如果想要自行封裝基本類型值,可以使用 Object(..) 函數(不帶 new 關鍵字):

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),但它們偶爾也會派上
用場。

3.3 拆封

如果想要得到封裝對象中的基本類型值,可以使用 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

在需要用到封裝對象中的基本類型值的地方會發生隱式拆封。具體過程(即強制類型轉
換)

var a = new String( "abc" );
var b = a + ""; // b的值為"abc"
typeof a; // "object"
typeof b; // "string"

3.4 原生函數作為構造函數

關于數組( array)、對象( object)、函數( function)和正則表達式,我們通常喜歡以常
量的形式來創建它們。實際上,使用常量和使用構造函數的效果是一樣的(創建的值都是
通過封裝對象來包裝)。
如前所述,應該盡量避免使用構造函數,除非十分必要,因為它們經常會產生意想不到的
結果。

3.4.1 Array(..)

var a = new Array( 1, 2, 3 );
a; // [1, 2, 3]
var b = [1, 2, 3];
b; // [1, 2, 3]

構造函數 Array(..) 不要求必須帶 new 關鍵字。不帶時,它會被自動補上。
因此 Array(1,2,3) 和 new Array(1,2,3) 的效果是一樣的。

Array 構造函數只帶一個數字參數的時候,該參數會被作為數組的預設長度( length),而
非只充當數組中的一個元素。
這實非明智之舉:一是容易忘記,二是容易出錯。
更為關鍵的是,數組并沒有預設長度這個概念。這樣創建出來的只是一個空數組,只不過
它的 length 屬性被設置成了指定的值。
如若一個數組沒有任何單元,但它的 length 屬性中卻顯示有單元數量,這樣奇特的數據結
構會導致一些怪異的行為。而這一切都歸咎于已被廢止的舊特性(類似 arguments 這樣的
類數組)。

我們將包含至少一個“空單元”的數組稱為“稀疏數組”。

var a = new Array( 3 );
a.length; // 3
a;

a 在 Chrome 中顯示為 [ undefined x 3 ](目前為止),這意味著它有三個值為 undefined
的單元,但實際上單元并不存在(“空單元” 這個叫法也同樣不準確)。

var a = new Array( 3 );
var b = [ undefined, undefined, undefined ];
var c = [];
c.length = 3;
a;
b;
c;

我們可以創建包含空單元的數組,如上例中的 c。只要將 length 屬性設置為
超過實際單元數的值,就能隱式地制造出空單元。另外還可以通過 delete
b[1] 在數組 b 中制造出一個空單元。

b 在當前版本的 Chrome 中顯示為 [ undefined, undefined, undefined ],而 a 和 c 則顯示
為 [ undefined x 3 ]。是不是感到很困惑?

更令人費解的是在當前版本的 Firefox 中 a 和 c 顯示為 [ , , , ]。仔細看來,這其中有三
個逗號,代表四個空單元,而不是三個。

Firefox 在輸出結果后面多添了一個 ,,原因是從 ES5 規范開始就允許在列表(數組值、屬性列表等)末尾多加一個逗號(在實際處理中會被忽略不計)。所以如果你在代碼或者調
試控制臺中輸入 [ , , , ],實際得到的是 [ , , ](包含三個空單元的數組)。這樣做雖
然在控制臺中看似令人費解,實則是為了讓復制粘貼結果更為準確。

針對這種情況, Firefox 將 [ , , , ] 改為顯示 Array [<3 empty slots>],這
無疑是個很大的提升。

更糟糕的是,上例中 a 和 b 的行為有時相同,有時又大相徑庭:

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(..) 之所以執行失敗,是因為數組中并不存在任何單元,所以 map(..) 無從遍歷。而
join(..) 卻不一樣,它的具體實現可參考下面的代碼:

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(..) 首先假定數組不為空,然后通過 length 屬性值來遍歷其中的元
素。而 map(..) 并不做這樣的假定,因此結果也往往在預期之外,并可能導致失敗。

我們可以通過下述方式來創建包含 undefined 單元(而非“空單元”)的數組:

var a = Array.apply( null, { length: 3 } );
a; // [ undefined, undefined, undefined ]

apply(..) 是一個工具函數,適用于所有函數對象,它會以一種特殊的方式來調用傳遞給
它的函數。

假設在 apply(..) 內部該數組參數名為 arr, for 循環就會這樣來遍歷數組: arr[0]、
arr[1]、 arr[2]。 然 而, 由 于 { length: 3 } 中 并 不 存 在 這 些 屬 性, 所 以 返 回 值 為
undefined

換句話說,我們執行的實際上是 Array(undefined, undefined, undefined),所以結果是單
元值為 undefined 的數組,而非空單元數組。

雖然 Array.apply( null, { length: 3 } ) 在創建 undefined 值的數組時有些奇怪和繁瑣,
但是其結果遠比 Array(3) 更準確可靠。

總之, 永遠不要創建和使用空單元數組。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,179評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,628評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,444評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,948評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,185評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,717評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,794評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,414評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,750評論 2 370

推薦閱讀更多精彩內容