JavaScript数据类型检测
对于确定一个js变量的类型,我们大概都知道几种方式。对于弱语言来说,检测其正确的类型是一件不是特别容易的事情。那么我们有几种方式来检查一个js变量的类型?
**从大的方向来说,js的数据类型分为基本数据类型和引用数据类型。**对于js的基本类型和引用类型是那些,这里就不在赘述。此外,在ES6新增的 symbol基本类型,ES7新增bigint大数类型,也是基本类型。
一般获取一个js类型的方式,大概应该是四种(我目前只知道这四种)
- typeof **
- instanceof
- toString -> Object.prototype.toString.call(变量) **
- constructor
注: ** 表示最常见的检测类型的方案
typeof底层原理
我们检测一个变量的类型,最常用的应该就是typeof运算符了。
typeof "你好"
//'string'
typeof 12
//'number'
typeof 12.3
//'number'
typeof 12n
//'bigint'
typeof Symbol()
//'symbol'
typeof null
//'object'
typeof undefined
//'undefined'
typeof []
//'object'
typeof {}
//'object'
typeof function(){}
//'function'
typeof new Object
//'object'
typeof new Boolean
//'object'
typeof new Set
//'object'
结果很直观。对于基本数据类型,什么字符串啊,数字啊,都能直接判断出来其对应的类型,对于函数类型也可以直接判断,但是像内置对象(标准,非标准)等,其结果都是得到一个模糊的 ‘object’。很明显是不利于我们判断其更加准确的类型。而且,对于一个null,也就是空,其结果居然也是 ‘object’,其实是让人有些费解的。
首先不考虑那么多,要明白对象 typeof 变量,该求值得到的 结果一定是一个字符串,毋庸置疑。一个简单的面试题:
typeof typeof typeof "123" // ???
typeof在检查基本(原始值)类型时还是很好用的,但是到了对象不是很好用了。
typeof的优势就是使用起来简单,基本数据类型值基本上都可以有效检查
那么typeof底层是如何判断出其具体的类型?
其底层是按照二进制的方式来检查其类型的。[效率高]
我们知道计算机的底层都是一串串的二进制代码。所以以二进制的形式进行判断,其效率肯定会高一些。不要反驳:哪怕这种方式只是效率高那么一点点,也是效率提升了。
以64位二进制存储变量去具体值在堆(原始类型可以在栈中)中的地址:以下是以某些数字开头的变量存储的变量的类型
-
二进制开头数字 类型
-
000 对象
-
1 整数
-
010 浮点数
-
100 字符串
-
110 布尔
-
000000…. null
-
-2^30 undefined
-
……
可以看见对象的地址其二进制是以000开头的,而null变量的地址全是0,所以在判断到以000开头以后,就直接认为null也是个对象。
因而导致我们使用typeof判断null的类型的时候,得到的类型并不是 ‘null’,而是 ‘object’。
也要记住,函数也是对象,是一种特殊的对象
如何判断一个变量的类型是否是对象?
function isObject(val){
if(val === null) return false;
return typeof val === 'object' || typeof val === 'function';
}
typeof 还有个特点:就是如果后面跟着的变量是未定义的,那么其得到的结果是 ‘undefined’,而不是报错
instanceof方式
[实例] instanceof [类]:用来检测当前实例是否属于这个类。也可以认为,这个类是否出现在这个实例的原型上。
let arr = [];
console.log(arr instanceof Array);//true
优势:基于这种方式,可以弥补typeof无法细分引用类型的缺点。
弊端:
-
arr是数组,但是arr也是一个对象
console.log(arr instanceof Object)//true
-
无法通过检测结果来说明,arr是一个普通对象。因为数组对象,正则对象等,都是Object类的实例,而这些对象都不是普通对象。
-
只要我们检测的类出现在了实例的原型链上,结果都是true,所以通过继承,修改原型链等方式,都会对结果有影响
-
instanceof检测的实例,必须都是引用数据类型的,它对基本类型值的操作无效
console.log(10 instanceof Number);// false console.log(new Number(10) instanceof Number);//true
constructor
利用类和实例的关系,因为实例的constructor
一般都等于类.property.constructor
,也就是当前类本身,(前提是原型的constructor
没有被破坏)
console.log([].constructor === Array)// true
const obj = {}
console.log(obj.constructor === Object);// true
console.log([].constructor === Object);// false
const num = 10
console.log(10.constructor === Number) ;// true
使用constructor来检测,有时候效果比instanceof更好,因为对于一个实例,其constructor属性都是确定唯一的,所以在没有肆意的胡乱修改的情况下,我们可以获取一个实例的真实类型,包括基本类型值都是可以获取到的。
**但是:constructor属性是很容易被修改的。**该属性是不被保护的。所以导致了这种检查方式并不保险。结果存在不确定性了。
不过没有人那么闲,去改内置类的constructor。
Object.property.toString.call([val])
JS中,唯一一种基本上不存在局限性的数据类型检测方式:Object.property.toString.call([val])
基于它,可以有效的检测任何数据类型的值。
const toString = Object.prototype.toString;
console.log(toString.call(10));
console.log(toString.call("10"));
console.log(toString.call(false));
console.log(toString.call(null));
console.log(toString.call(undefined));
console.log(toString.call(Symbol()));
console.log(toString.call(BigInt("10")));
console.log(toString.call({}));
console.log(toString.call([]));
console.log(toString.call(/\.js$/));
console.log(toString.call(function fn(){}));
结果的结构:[object 真实类型(所属的构造函数)]
每一种数据类型构造函数的原型上都有toString
方法。
- 每一种基本数据类型的原型上都有一个
valueOf
方法。 - Object的原型上也有
valueOf
方法 - 除了Object.property上的toString是用来返回当前实例所属类的信息(检测数据类型的),其余的都是转换为字符串的
const toString = Object.prototype.toString;
console.log((10).toString())//10
console.log("11".toString())//11
console.log((function () { }).toString())//function (){}
console.log(([1, 2, 3]).toString())//1,2,3
console.log(({}).toString()) // [object Object]
对象实例.toString():toString方法中的this是对象实例,也就是检测它的数据类型。也就是说this是谁,就是检测谁的数据类型。所以我们是把toString执行,基于call改变this指向要检测的值
const toString = Object.prototype.toString;
let num1 = 10, num2 = new Number(10);
let arr = [10, 20],
obj = {
name: "张三",
age: 22
}
// valueOf 方法 是获取原始值的 [[PrimitiveValue]]
// toString 是将获取到的原始值转为字符串
console.log(num1, num1.valueOf(), num1.toString())
console.log(num2, num2.valueOf(), num2.toString())
引用类型
// 除了基本数据类型的包装类,其他的引用类型上都是没有valueOf方法的
// 所以我们对对象,数组等 获取valueOf方法都是获取到Object原型上的
// arr.toString 获取的是自己原型上的toString
console.log(arr, arr.valueOf(), arr.toString())
console.log(obj, obj.valueOf(), obj.toString())
valueOf和toString的关系
-
alert会把所有要输出的值转为字符串(隐式转换),像这种隐形转换为字符串的有很多
-
例如: 字符串拼接,把对象转换为数字(也是先转为字符串)…
-
alert(num1) // num1.valueOf 获取原始值 [[PrimitiveValue]] -> [[PrimitiveValue]].toString() 转为字符串
// alert会把所有要输出的值转为字符串(隐式转换),像这种隐形转换为字符串的有很多
// 例如: 字符串拼接,把对象转换为数字(也是先转为字符串)...
// alert(num1) // num1.valueOf 获取原始值 -> 原始值.toString() 转为字符串
面试题
让a取什么值,可以保证下面的条件一定成立?
let a = ?;
if (a === 1 && a === 2 && a === 3) {
console.log("ok")
}
方式一:普通对象
let a =
// 方法1 a是一个对象 实现toString或者valueOf方法
{
n: 0,
// 实现toString或者valueOf
// toString() {
// return ++this.n
// }
valueOf() {
return ++this.n
}
};
if (a == 1 && a == 2 && a == 3) {
console.log("ok")
}
方式二:数组对象
// 方式2:实现valueOf方法 或者toString方法
a = [1, 2, 3];
a.valueOf = function(){
return this.shift()
}
a.toString = function () {
return this.shift()
}
if (a == 1 && a == 2 && a == 3) {
console.log("ok")
}
方式三:某些数据类型包装类,引用类
例如:String,Number,Boolean,函数等
// a = new String();
// a = new Number();
// a = new Boolean();
// a = function(){};
a = new Number();
let value = 1;
a.valueOf = function () {
return value++;
}
if (a == 1 && a == 2 && a == 3) {
console.log("ok")
}