在 JavaScript 中,有 7 种内置类型,分别是:
- 空值 (
null
) - 未定义 (
undefined
) - 布尔值 (
boolean
) - 数字 (
number
) - 字符串 (
string
) - 对象 (
object
) - 符号 (
symbol
)
严格来说,JavaScript 有 6 种原始类型(值类型):
- 空值 (
null
) - 未定义 (
undefined
) - 布尔值 (
boolean
) - 数字 (
number
) - 字符串 (
string
) - 符号 (
symbol
)
此外,还有一个特殊的原始类型:
- 任意精度整数 (
BigInt
),用于表示大于Number
范围的整数。
这些原始类型是不可变的,而 Object
类型是复杂类型,可以包含多个值。
什么是 typeof
typeof 是 JavaScript 中的一个运算符,用来检查一个变量的类型。它会返回一个字符串,表示给定值的类型。
接下来我们使用 typeof
运算符来查看值的类型,它返回的是类型的字符串值。有意思的是,这七种类型和它们的字符串值并不一一对应:
console.log(typeof 777); // number
console.log(typeof 3.14); // number
console.log(typeof 0); // number
console.log(typeof Infinity); // number
console.log(typeof Number('moment')); // number
console.log(typeof 77n); // bigint
console.log(typeof '1'); // string
console.log(typeof typeof 1); // string typeof 返回一个字符串
console.log(typeof String(777)); // string
console.log(typeof true); // boolean
console.log(typeof false); // boolean
console.log(typeof Boolean(5)); // boolean // Boolean() 会基于参数是真值还是虚值进行转换
typeof !!1 === 'boolean'; // 两次调用 !(逻辑非)运算符相当于 Boolean()
console.log(typeof Symbol()); // symbol
console.log(typeof Symbol('foo')); // symbol
console.log(typeof Symbol.iterator); // symbol
console.log(typeof { a: 1 }); // object
console.log(typeof [1, 2, 4]); // object
console.log(typeof new Date()); // object
console.log(typeof /regex/); // object
console.log(typeof null); // object
console.log(typeof function () {}); // function
console.log(typeof class T {}); // function
你可能注意到 null
类型比较特殊,typeof null==='object'
,正确的返回应该是'null'
,但是这个 bug 由来已久,在JavaScript
中已经存在了将近二十年,也许永远不会修复。
在 JavaScript 最初的实现中,JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是 0。由于 null
代表的是空指针(大多数平台下值为 0x00),因此,null 的类型标签是 0,typeof null
也因此返回 "object"
。
console.log(typeof function () {}); // function
在上面的代码中,输出结果为 'function'
,说明 function
也是JavaScript
的一个内置类型。然而规范里,它实际上是 object
的一个 子类型
。具体来说,函数是 可调用对象
,它有一个内部属性 [[call]]
,该属性使其可以被调用。
function f() {}
console.log(f.__proto__.constructor === Function); // true
console.log(f.__proto__.__proto__.constructor === Object); // true
// 函数不仅是对象,还可以拥有属性
console.log(f.name); // f
console.log(f.arguments); // 因为没有传参数,所以是一个 null
通过原型的方法去判别,函数确实是 Object 的子类。
再来看看数组,JavaScript
支持数组,数组也是对象,确切涞水,它也是 object
的一个 子类型
,数组的元素按数字顺序来进行索引,其 length
属性是元素的个数。
const foo = [];
console.log(foo.__proto__.constructor === Array); // true
console.log(foo.__proto__.__proto__.constructor === Object); // true
new 操作符
- 所有使用
new
调用的构造函数都将返回非基本类型,返回的不是object
类型,就是function
。大多数返回对象,但值得注意的例外是 Function,它返回一个函数。
const str = new String('777');
const num = new Number(777);
const func = new Function();
console.log(typeof str); // object
console.log(typeof num); // object
console.log(typeof func); // function
undefined 和 undeclared
- 变量在未持有值的时候为
undefined
。此时typeof
返回undefined
:
var a;
console.log(tyoeof a); // undefined
大多数的开发者倾向于将 undefined
等同于 undeclared(未声明)
,但在 JavaScript
中它们完全是两回事。在作用域中声明但是还没有赋值的变量,是 undefined
。相反,还没有在作用域中声明过的变量,是undeclared
的。
在上列中, bar is not defined
容易让人误以为是 bar is undefined
。但是 undefined
和 is undefined
是两码事,但是 typeof
处理 undeclared
返回的结果竟然是 undefined
,例如:
var foo;
console.log(typeof foo); // undefined
console.log(typeof bar); // undefined
它们两个原样返回 "undefined"
,并且 typeof bar
并没有报错,这是因为 typeof
有一个特殊的安全防范机制。
内部属性 [[class]]
在前面的例子中,使用 typeof
进行判断,无论是 null
、Object
、Array
等类型,都返回的是 "object"
,那么是否有一种机制可以判断它具体为什么类型的值呢?答案是有的。
所有 typeof
返回值为 object
的对象(如数组)都包含一个内部属性 [[class]]
,我们可以把它看作一个内部的分类,而非传统的面向对象意义上的类。这个属性无法直接访问,一般通过 Object.prototype.toString(...)
来查看。例如:
console.log(Object.prototype.toString.call([1, 2, 3])); // [object Array]
console.log(Object.prototype.toString.call(1)); // [object Number]
console.log(Object.prototype.toString.call('moment')); //[object String]
console.log(Object.prototype.toString.call(true)); //[object Boolean]
console.log(Object.prototype.toString.call(null)); // [object Null]
console.log(Object.prototype.toString.call(undefined)); // [object Undefined]
console.log(Object.prototype.toString.call(function f() {})); // [object Function]
console.log(Object.prototype.toString.call(class C {})); // [object Function]
console.log(Object.prototype.toString.call(new Date())); // [object Date]
console.log(Object.prototype.toString.call(Symbol())); // [object Symbol]
console.log(Object.prototype.toString.call(new Boolean(1))); // [object Boolean]
console.log(Object.prototype.toString.call(new RegExp())); // [object RegExp]
上例中,数组内部[[class]]属性值是 Array
,正则表达式的值是 RegExp
。多数情况下,对象的内部 [[class]]
属性和创建该对象的内建原生构造函数相对应,但并不是所有的情况都是这样,例如一些基本类型,例如 null
和 undefined
,虽然 Null()
和 undefined()
这样的原生构造函数并不存在,但是内部 [[class]]
属性值仍然是 Null
和 Undefined
。
其他基本类型,例如 字符串、数值和布尔值 的情况有所不同,由于基本类型值没有 .length
和 .toString()
这样的属性和方法,需要通过封装对象才能访问,此时 JavaScript
会自动为基本类型封装为一个对象,例如 var foo = 'moment';
,实际上进行的是 var foo =new String('moment');
,使其变成一个对象,让其拥有自己的属性和方法,如果想要得到封装对象中的基本类型值,可以使用 valueOf()
函数,例如:
var foo = new String('moment');
console.log(foo); // [String: 'moment']
console.log(foo.valueOf()); // moment
console.log(typeof foo.valueOf()); // string
手写 typeof
typeof
是非常有用的,但它不像需要的那样万能。例如,typeof []
是 "object"
,以及 typeof new Date()
、typeof /abc/
等。
为了明确地检查类型, mdn
上提供了一个自定义的 type(value)
函数,它主要模仿 typeof
的行为,但对于非基本类型(即对象和函数),它在可能的情况下返回更详细的类型名。
function type(value) {
// 如果传入的值是 null ,则返回 null
if (value === null) {
return 'null';
}
const baseType = typeof value;
// 如果是基本类型
if (!['object', 'function'].includes(baseType)) {
return baseType;
}
// Symbol.toStringTag 通常指定对象类的“display name”
const tag = value[Symbol.toStringTag];
if (typeof tag === 'string') {
return tag;
}
// 如果他是一个函数,其源代码以 class 关键字开头的
if (baseType === 'function' && Function.prototype.toString.call(value).startsWith('class')) {
return 'class';
}
// 构造函数的名称;例如 `Array`、`GeneratorFunction`、`Number`、`String`、`Boolean` 或 `MyCustomClass`
const className = value.constructor.name;
if (typeof className === 'string' && className !== '') {
return className;
}
// 没有合适的方法来获取值的类型,直接返回
return baseType;
}
参考文献
-
书籍
你不知道的JavaScript
总结
typeof
运算符用于检查变量的类型,返回一个表示数据类型的字符串。对于基本类型(如 number
、string
等),返回值明确,但对于对象类型(如 null
、数组、正则表达式等),typeof
都返回 "object"
,这导致一些特殊情况。为了解决这种模糊判断问题,可以通过 Object.prototype.toString.call()
来精确判断对象的实际类型。此外,typeof
无法判断数组、null
和类等,需要额外的处理。