封箱包装器
这些对象包装器服务于一个非常重要的目的。基本类型值没有属性或方法,所以为了访问 .length
或 .toString()
你需要这个值的对象包装器。值得庆幸的是,JS 将会自动地 封箱(也就是包装)基本类型值来满足这样的访问。
var a = "abc";
a.length; // 3
a.toUpperCase(); // "ABC"
那么,如果你想以通常的方式访问这些字符串值上的属性/方法,比如一个 for
循环的 i < a.length
条件,这么做看起来很有道理:一开始就得到一个这个值的对象形式,于是 JS 引擎就不需要隐含地为你创建一个。
但事实证明这是一个坏主意。浏览器们长久以来就对 .length
这样的常见情况进行性能优化,这意味着如果你试着直接使用对象形式(它们没有被优化过)进行“提前优化”,那么实际上你的程序将会 变慢。
一般来说,基本上没有理由直接使用对象形式。让封箱在需要的地方隐含地发生会更好。换句话说,永远也不要做 new String("abc")
、new Number(42)
这样的事情 —— 应当总是偏向于使用基本类型字面量 "abc"
和 42
。
对象包装器的坑
如果你 确实 选择要直接使用对象包装器,那么有几个坑你应该注意。
举个例子,考虑 Boolean
包装的值:
var a = new Boolean( false );
if (!a) {
console.log( "Oops" ); // 永远不会运行
}
这里的问题是,虽然你为值 false
创建了一个对象包装器,但是对象本身是“truthy”(见第四章),所以使用对象的效果是与使用底层的值 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
),但你可能会遇到一些它们有用的罕见情况。