数据类型
在 JavaScript 中有 8 种基本的数据类型(7 种原始类型和 1 种引用类型),它们分别是:
原始类型/基本类型:
Number
,BigInt
,String
,Boolean
,null
,undefined
,Symbol
引用类型/复杂类型:
Object
typeof 运算符
typeof
运算符返回参数的类型。当我们想要分别处理不同类型值的时候,或者想快速进行数据类型检验时,非常有用。
对 typeof x
的调用会以字符串的形式返回数据类型:
注意:返回的结果是字符串,而且全部都是小写
console.log(typeof 0); // "number"
console.log(typeof 10n); // "bigint"
console.log(typeof "foo"); // "string"
console.log(typeof true); // "boolean"
console.log(typeof undefined); // "undefined"
console.log(typeof Symbol("id")); // "symbol"
function cb() {
console.log("hello");
}
console.log(typeof cb); //"function"
console.log(typeof null); //"object"
console.log(typeof [1, 2, 3]); //"object"
console.log(typeof { a: "a", n: "b" }); //"object"
let map = new Map();
let set = new Set();
console.log(typeof map); //"object"
console.log(typeof set); //"object"
console.log(typeof /[0-9]/); //"object"
console.log(typeof new Date()); //"object"
根据上面的结果可以发现,下面的六种基本类型以及函数类型,typeof
可以正确的区分:
console.log(typeof 0); // "number"
console.log(typeof 10n); // "bigint"
console.log(typeof "foo"); // "string"
console.log(typeof true); // "boolean"
console.log(typeof undefined); // "undefined"
console.log(typeof Symbol("id")); // "symbol"
function cb() {
console.log("hello");
}
console.log(typeof cb); //"function"
但是对于null
和数组
,以及普通对象
,正则
以及map
,set
等类型,则不能更精细的区分,返回的都是object
。
其中typeof null
会返回 "object"
—— 这是 JavaScript 编程语言的一个错误,实际上它并不是一个 object
。
instanceof 操作符
instanceof
运算符用于检测构造函数的prototype
属性是否出现在某个实例对象的原型链上,使用方法如下:
//实例对象 instanceof 构造函数
object instanceof constructor
返回结果为true
或者false
。
如果构造函数的prototype
属性在object
的原型链上,就返回true
,否则返回false
console.log([1, 2, 3] instanceof Array); //true
console.log({ a: "a", n: "b" } instanceof Object); //true
console.log(null instanceof Object); //false
let map = new Map();
let set = new Set();
let time = new Date();
console.log(map instanceof Map); //true
console.log(set instanceof Set); //true
console.log(time instanceof Date); //true
console.log(/[0-9]/ instanceof RegExp); //true
使用instanceof
可以准确地判断复杂引用数据类型,但是不能正确判断基础数据类型:
关于instanceof
的实现原理,可以参考下面:
// 左边是对象,右边是构造函数
// 原理就是判断左边对象的原型链上,有没有构造函数的prototype属性的对象
function myInstanceOf(left, right) {
if (typeof left !== "object" || left === null) return false;
let obj = Object.getPrototypeOf(left);
while (obj) {
if (right.prototype === obj) return true;
obj = Object.getPrototypeOf(obj);
}
return false;
}
更通用的数据类型检测
Object.prototype.toString()
,调用该方法,统一返回格式“[object Xxx]”
的字符串:
Object.prototype.toString({}) // "[object Object]"
Object.prototype.toString.call({}) // 同上结果,加上call也ok
Object.prototype.toString.call(1) // "[object Number]"
Object.prototype.toString.call('1') // "[object String]"
Object.prototype.toString.call(true) // "[object Boolean]"
Object.prototype.toString.call(function(){}) // "[object Function]"
Object.prototype.toString.call(null) //"[object Null]"
Object.prototype.toString.call(undefined) //"[object Undefined]"
Object.prototype.toString.call(/123/g) //"[object RegExp]"
Object.prototype.toString.call(new Date()) //"[object Date]"
Object.prototype.toString.call([]) //"[object Array]"
Object.prototype.toString.call(document) //"[object HTMLDocument]"
Object.prototype.toString.call(window) //"[object Window]"
简单说下我的理解,在js中除了基本数据类型以外,其他的类型都可视为对象类型,当然也可以使用包装类,将基本类型包装成对象,比如字符串字面量在调用toUpperCase()
方法时就会自动将其包装为String
对象,使得你可以调用字符串方法:
let str = "hello";
console.log(str.toUpperCase()); // 输出 "HELLO"
可以认为所有的对象都继承自Object.prototype
对象,那么所有的对象类型共享从Object.prototype
继承来的属性和方法,比如toString()
方法。
Object.prototype.toString() 返回 “[object Type]”,这里的 Type 是对象的类型。如果对象有
Symbol.toStringTag 属性,其值是一个字符串,则它的值将被用作 Type。许多内置的对象,包括 Map 和Symbol,都有 Symbol.toStringTag。一些早于 ES6 的对象没有
Symbol.toStringTag,但仍然有一个特殊的标签。它们包括(标签与下面给出的类型名相同):
arguments 对象返回 “[object Arguments]”。其他所有内容,包括用户自定义的类,除非有一个自定义的 Symbol.toStringTag,否则都将返回 "[object Object]"。
Object.prototype.toString()
返回的类型是根据对象的Symbol.toStringTag
属性来的,对于用户自定义类,toStringTag
默认使用"Object"
标签。
class ValidatorClass {}
Object.prototype.toString.call(new ValidatorClass()); // "[object Object]"
看下面的代码:
这里我们自定义类,接着设置自己的自定义标签。然后创建ValidatorClass
类对应的实例对象v
,使用Object.prototype.toString()
判断类型,可以发现,这个过程是从v
实例的构造函数中的Symbol.toStringTag
接口中寻找的。
class ValidatorClass {
get [Symbol.toStringTag]() {
return "Validator";
}
}
let v = new ValidatorClass();
console.log(Object.prototype.toString.call(v)); // "[object Validator]"
这也说明,如果是字符串,数字类型,应该是去String,Number构造函数中找对应的接口。
ECMA文档的描述:
该属性值是一个字符串,用来创建对象的默认字符串描述。可以通过内置方法Object.prototype.toString()
获取。
另外关于为什么需要使用call()
绑定到目标对象:
这是因为js
中的其他对象都继承自Object.prototype
,比如String类型,Array类型,都可以调用toString()方法,但是同时他们也重写了toString()方法,和最初的,即来自Object.prototype
对象的toString()不一样了,比如Number.prototype.toString()
,返回的是表示该数字值的字符串:
let n = 123;
console.log(n.toString()); // "123"
console.log(typeof n.toString()); //string
所以需要使用最初的toString()方法,同时改变其this的指向。