深入理解js类型判断
前言
- 在开始本篇讲解之前,你可能需要一些原型的前置知识,如果还不是很懂,可以通过 原型链 这篇文章进行学习或者巩固。我们先来了解一下JavaScript的数据结构,我们很快就会被用到。
JavaScript 7种内置类型
- 空值(
null
)、未定义(undefined
)、布尔值(boolean
)、数字(number
)、字符串(string
)、对象(object
)、符号(symbol
)。
JavaScript 7种原始类型
- 空值(
null
)、未定义(undefined
)、布尔值(boolean
)、数字(number
)、字符串(string
)、 任意精度整数(Bigint
)、符号(symbol
)。
什么是 typeof
typeof
用于判断一个变量的是什么类型,通过typeof
判断可以返回number
、string
、object
,boolean
,function
、undefined
、symbol
这七种基本类型- 其基本语法是以下这样的形式:
// operand 表示要返回类型的对象或基本类型的表达式
typeof operand
typeof (operand)
typeof 基本用法
- 接下来我们使用
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
- mdn文档
转载自:https://juejin.cn/post/7166811885742850056