为了方便操作原始值,ECMAScript 提供了 3 种特殊的引用类型:Boolean、Number 和 String 每当用到某个原始值的方法或属性时,都会隐式创建一个相应原始包装类型的对象,从而暴露出操作原始值的各种方法
举个例子
当我们想要截取一个字符串第二个索引之后的字符时我们可以通过类似的操作
let s1 = "some text";
let s2 = s1.substring(2);
s1是一个字符串原始值,第二行紧接着在s1上调用了substring()
方法,并把结果保存在s2中
但是原始值本身不是对象,因此逻辑上不应该有方法
而实际上运行是没问题的。为什么呢?
是因为 当第二行访问 s1 时,是以读模式访问的,也就是要从内存中读取变量保存的值。在以读模式访问字符串值的任何时候,js内部都会执行以下 3 步:
- 创建一个 String 类型的实例
- 调用实例上的特定方法
- 销毁实例
可以把这 3 步想象成执行了如下 3 行 ECMAScript 代码:
let s1 = new String("some text");
let s2 = s1.substring(2);
s1 = null;
这种行为可以让原始值值拥有对象的行为 (继承) 对于boolean和number类型类似,只不过 使用的是 Boolean 和 Number 包装类型而已
问题引伸
引用类型与原始值包装类型的主要区别在于对象的生命周期。 在通过 new 实例化引用类型后,得到的实例会在离开作用域时被销毁,而自动创建的原始值包装对象则只存在于访问它的那行代码执行期间。
这意味着不能在运行时给原始值添加属性和方法。比如下面的例子:
let s1 = "some text";
s1.color = "red";
console.log(s1.color); // undefined
第二行想要给s1添加一个color属性,但是第三行代码访问s1的color属性时却undefined,原因就第二行代码运行时会临时创建一个 String
对象,而当第三行代码执行时这个对象s1.color已经被销毁了
其实可以显式地使用 Boolean、Number 和 String 构造函数创建原始值包装对象。但没必要。。每次变量初始化一个索引let i = new Number(0)
看着也别扭
Object构造函数妙用
Object 构造函数作为一个工厂方法,能够根据传入值的类型返回相应原始值包装类型的实例。比如:
let obj = new Object("some text");
console.log(obj instanceof String); // true
注意,使用 new 调用原始值包装类型的构造函数,与调用同名的转型函数并不一样。例如:
let value = "25";
let number = Number(value); // 转型函数
console.log(typeof number); // "number"
let obj = new Number(value); // 构造函数
console.log(typeof obj); // "object"
这是因为使用new时会创建一个全新的包装对象,而并不是对原始值进行包装
小小总结
虽然不推荐显式创建原始值包装类型的实例,但它们对于操作原始值的功能是很重要的(继承自包装类型的prototype)
参考
《JavaScript 高级程序设计 第四版》