第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阅读 213,186评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,858评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,620评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,888评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,009评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,149评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,204评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,956评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,385评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,698评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,863评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,544评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,185评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,899评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,141评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,684评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,750评论 2 351

推荐阅读更多精彩内容