引子
你有没有思考过一个问题,一个 number 类型的原始值,为什么可以调用方法。比如说:
let num = 12;
let str = num.toString();
console.log(typeof str); // string
num
难道不是一个原始类型吗?没错,它是原始类型。那既然如此,为什么会有方法?
这就要提到一个东西,名为包装类。
包装类
概述
JavaScript为了方便我们操作原始值,为我们提供了一种特殊的类,名为包装类。
Number
String
Boolean
Symbol
BigInt
每当我们用到这些原始值的方法或者属性的时候,JavaScript 帮我们自动创建一个相应的原始包装类对象。
我们用包装类的思想分析一下下面的代码:
let num = 12;
let str = num.toString();
console.log(typeof str); // string
在执行 num.toString()
的时候发生了什么:
- 创建一个
Number
类型的实例对象,并且这个对象的值为 12 - 对这个对象调用它的方法
toString()
,这个方法正好会返回一个字符类型的 12 - 执行完之后,销毁这个对象
转成代码,我们可以这么理解这三个步骤
let newObj = new Number(12);
// 返回 '12'
newObj.toString();
// 销毁对象
newObj = null;
显式创建包装类
如果需要显式的创建包装对象,我们需要借用 Object()
。
const strObj = new Object('str');
const numObj = new Object(1);
const booleanObj = new Object(false);
const symbolObj = new Object(Symbol(1));
实际上,对于前面三种类型,我们还可以使用下面这种方式创建包装对象
const strObj = new String('str');
const numObj = new Number(1);
const boolObj = new Boolean(false);
但是并不推荐使用这种方式创建,因为 ES6 并不支持这几种方法,只不过是由于兼容问题,还能够创建。
使用建议
并不推荐显式的创建包装类的实例(尤其是 Boolean 类型),因为可能会存在一些问题。
let bool = new Object(false);
console.log(bool && true); // true
分析:
- 变量
bool
是一个对象,它的原始值是false
- 对象
bool
不是一个falsy值 - 所以 隐式转型之后,
bool
的 布尔值应该是true
- 因此会判断第二个,而第二个值是
true
,因此最后返回true
自动生成对象与显式生成对象的区别
所生成对象的生命周期不同。
- 通过 new 关键字实例化的对象,只会在离开作用域的时候销毁
- 通过包装类型自动生成的对象,在使用完之后直接就会销毁
注意措辞,这里说的是自动生成,也就是通过读模式访问的时候。也就是说,我们如果手动使用原始值包装类是不会被自动销毁的。
let num = 12;
let numStr = num.toString();
// 执行完之后, num所生成的对象就又被销毁了, 访问不到
let numObj = new Object(12);
// 我们手动创建的一个 Number 对象
// let numObj = new Number(12); 这样做也可以, 但是不推荐
console.log(numObj instanceof Number); // true
注意为什么不是 ?
由于Symbol和BigInt的出现,对它们调用new都会报错,目前ES6规范也不建议用new来创建基本类型的包装类。
总结
由于原始包装类型的存在,JavaScript中的原始值可以被当成对象来使用。有 5 种原始值包装类型:
Boolean
Number
String
Symbol
BigInt
它们都具有以下特点:
- 每种包装类型都映射到同名的原始类型
- 以读模式访问原始值的时候,后台会实例化一个原始值的包装类对象,借助这个对象可以操作对应的数据
- 语句执行完毕后,包装对象会被自动销毁