除了我们能自定义函数外,js中还具有一些原生函数,如String,Number,Boolean,Object,Function,Array,Date,RegExp,Error,Symbol等。可以看到有些内置类型和简单基本类型名字和很相似。
使用这些原生函数构造出来的对象,对其使用typeof时返回的都是object。
const str = new String("abc");
const obj = new Object({ a: 1 });
console.log(typeof str); // object
console.log(typeof obj); // object
内部属性[[Class]]
所有使用typeof返回为object的对象,内部都包含一个[[Class]]属性。该属性无法直接访问,一般通过Object.prototype.toString查看:
const str = new String("abc");
const obj = new Object({ a: 1 });
const arr = new Array(1, 2, 3);
console.log(Object.prototype.toString.call(str)); // [object String]
console.log(Object.prototype.toString.call(obj)); // [object Object]
console.log(Object.prototype.toString.call(arr)); // [object Array]
多数情况下,对象内部的[[Class]]属性和创建该对象的原生构造函数对应。但是null和undefined例外:
console.log(Object.prototype.toString.call(undefined)); // [object Undefined]
console.log(Object.prototype.toString.call(null)); // [object Null]
事实上,Undefined和Null这样的原生函数并不存在。
装箱拆箱
我们都知道基本类型(如boolean,number,string)并不是对象,但实际使用时,仍然可以像使用对象一样调用方法:
const strPrimitive = "primitive string";
const strObject = new String("object string");
console.log(typeof strPrimitive); // string
console.log(typeof strObject); // object
console.log(strPrimitive.toUpperCase()); // PRIMITIVE STRING
console.log(strObject.toUpperCase()); // OBJECT STRING
上述代码中strPrimitive并不是一个对象,它只是一个不可变的字面量。在需要对它进行一些操作时,如toUpperCase,js会自动将它转换为一个String对象,从而获取这些方法和属性。所以我们并不需要使用构造形式创建一个简单基本类型。这就是装箱操作(也叫做封装对象)。
类似的,当我们声明其他基本变量(number,boolean)时,当我们在调用某些属性时,都会被自动转换为对应的对象,相当于是new SomeType()。当我们声明对象类型变量时,也会在声明后自动变成封装对象。所以它们都能访问对应构造函数中的方法。
const str = "string";
str.slice(); //等价于 new String("string").slice();
const num = 1;
num.toFixed(); //等价于 new Number(1).toFixed()
const bool = true;
bool.valueOf(); //等价于 new Boolean(true).valueOf()
const obj = {}; // 等价于 new Object()
function foo() {} // 等价于 new Function("{}")
const arr = [1, 2]; // 等价于 new Array(1, 2)
由于js存在封装对象的特性,所以我们定义变量时尽量直接使用基本类型值,而不使用构造形式。
如果想要得到封装对象中的基本类型值,可使用valueOf方法进行拆箱解封:
const a = new String("abc");
const b = new Number(10);
const c = new Boolean(true);
console.log(a.valueOf()); // "abc"
console.log(b.valueOf()); // 10
console.log(c.valueOf()); // true
在需要用到封装对象的基本类型值时,会发生隐式拆箱。
const a = new String("abc");
const b = a + "!";
console.log(b); // abc!
console.log(typeof b); // string