接近完美地判断JS数据类型,可行吗

做者: JowayYoung
仓库: GithubCodePen
博客: 掘金思否知乎简书头条CSDN
公众号: IQ前端
联系我:关注公众号后有个人 微信
特别声明:原创不易,未经受权不得对此文章进行转载或抄袭,不然按侵权处理,如需转载或开通公众号白名单可联系我,但愿各位尊重原创的知识产权

本文由笔者师妹LazyCurry创做,收录于笔者技术文章专栏下前端

前言

JS的变量与其余语言的变量有很大区别,由于其变量松散的本质,决定了变量只是在特定时间内用于保存特定值的一个名字而已,变量的值及其数据类型可在声明周期内改变。git

JS的数据类型可分为基本类型引用类型,先简单介绍两种数据类型,再来分析判断数据类型的几种方法。固然,这个也是大厂常考的面试题,同窗们可按照文章的思路进行回答和扩展,让面试官耳目一新。github

数据类型

基本类型

基本类型包括Undefined、Null、String、Number、Boolean、Symbol。基本类型按值访问,因此咱们可操做保存在变量中实际的值。面试

基本类型的值在内存中占据固定大小的空间,是被保存在栈内存中。从一个变量向另外一个变量复制基本类型的值,会建立这个值的一个副本,这两个值彻底独立地存放在栈内存中。segmentfault

引用类型

引用类型是对象类型,包括Object、Array、Function、Data、Regexp、Error。引用类型的值是保存在堆内存中的对象,JS不容许直接访问内存中的位置,也就是说不能直接访问操做对象的内存空间。数组

操做对象时,其实是在操做对象的引用,因此说引用类型的值是按引用访问的。从而有[1, 2] === [1, 2]false微信

判断数据类型

简单的讲完JS的两种数据类型,接下来介绍一下JS判断数据类型的4种方法。app

typeof

typeof是肯定一个变量是stringnumberbooleansymbol(ES6新增类型)仍是undefined的最佳工具。注意,这里并无说起null以及引用型数据。框架

typeof可能返回下面某个结果,结果的对应值以下:frontend

  • undefined:未定义的值
  • string:字符串
  • number:数值
  • boolean:布尔
  • symbol:惟一值
  • object:对象或空值(null)
  • function:函数
typeof undefined; // undefined
typeof null; // object
typeof "这是一段字符串"; // string
typeof 1; // number
typeof true; // boolean
typeof new Symbol(); // symbol
typeof new Object(); // object
typeof new Function(); // function
typeof new Date(); // object

上面的例子中,对于基本类型来讲,除开null均可返回正确的结果。调用typeof null会返回object,是由于null被认为是一个空的对象引用,所以返回了object,固然这个也是JS设计语言早期遗留的Bug。

而在其余引用类型,除开function均返回object类型,所以用typeof来判断引用类型数据的类型并不可取,typeof适合用来判断基础类型值。

instanceof

instanceof可用来判断一个实例对象是否属于一个构造函数,其表达式A instanceof B,若是A是B的实例,则返回true,不然返回false

实现原理其实就是在A的原型链上寻找是否有原型等于B.prototype,若是一直找到A原型链的顶端null,仍然找不到原型等于B.prototype,那么就可返回false。原型链的知识可戳往期文章《来自原形与原型链的拷问》回顾下哦,这里就再也不讲原型链啦~

new Date() instanceof Date; // true
new Date() instanceof Object; // true

[] instanceof Array; // true
[] instanceof Object; // true

function Person() {};
const person = new Person();
person instanceof Person; // true
person instanceof Object; // true

从上面的例子可看到,instanceof可判断出[]Array的实例,Date对象是Date的实例,personPerson构造函数的实例,到这里并没什么问题,可是instanceof认为这些也都是Object的实例,这就有点使人疑惑。

其实可根据instanceof的实现原理来分析一下,上面已经讲过实现原理,在这里咱们套用一下instanceof用于数组判断的过程。

[] instanceof Array,由于能找到[].__proto__指向Array.prototype,所以返回true。[] instanceof Object,在这里就是也是要沿着[]的原型链找,有[].__proto__指向Array.prototype,又由于Array.prototype默认是Object的实例,因此有Array.prototype.__proto__指向了Object.prototype,所以这就是为何instanceof认为[]也是Object的实例。

instanceof只能用来判断两个对象是否属于实例关系,并不能判断一个对象属于什么类型。简单说,就是判断两个类是否从属关系。

avatar

instanceof的问题在于,假如只有一个全局执行环境,若是网页中有两个框架,实际上就存在两个不用的全局执行环境,从而存在两个不一样版本的Array构造函数。若是从一个框架向另外一个框架传入一个数组,那么传入的数组与第二个框架中原生建立的数组分别是不一样的构造函数。

const iframe = document.createElement("iframe");
document.body.appendChild(iframe);
const IArray = window.frames[0].Array;
const iarr = new IArray();
iarr instanceof Array; // false
Array.isArray(iarr); // true

为了解决这个问题,ES5新增了Array.isArray(),这个方法能肯定某个值是否是数组或类数组。

constructor

上面提到的原型链,原型对象的constructor属性指向了构造函数,又由于实例对象的__proto__属性指向原型对象,所以可有:每个实例对象均可经过constructor来访问它的构造函数。而JS内置对象在内部构建时也是这么作的,所以可用来判断数据类型。

"".__proto__.constructor === String; // true
// 下面将属性__proto__去掉,效果相同
"".constructor === String; // true
new Number(1).constructor === Number; // true
true.constructor === Boolean; // true
[].constructor === Array; // true
new Date().constructor === Date; // true
new Function().constructor === Function; // true

可看出,大部分类型都能经过这个属性来判断。可是因为undefinednull是无效的对象,所以是没有constructor属性的,这两个值不能用这种方法判断。另外,当重写原型时,原型原有的constructor会丢失,这时判断也就不生效了。

function Person() {};
Person.prototype = {
    name: "XX"
};
const person = new Person();
person.constructor === Person; // false

这时打印person.constructor,可看到是一个Object。为何会变成Object呢?这是由于在从新定义原型时,传入的是一个对象{}{}new Object()的字面量,所以会将Object原型上的constructor传递给{},因此person.constructor也就打印出了Object。

所以,在重写原型对象时,都须要给constructor从新赋值,来保证对象实例的类型不改变。这个点在开发时记得记得注意!

toString

Object.prototype.toString方法返回对象的类型字符串,所以可用来判断一个值的类型。由于实例对象有可能会自定义toString方法,会覆盖Object.prototype.toString,因此在使用时,最好加上call。会有如下返回值:

  • [object Undefined]:未定义的值
  • [object Null]:空值
  • [object String]:字符串
  • [object Number]:数值
  • [object Boolean]:布尔
  • [object Symbol]:惟一值
  • [object Object]:对象
  • [object Array]:数组
  • [object Function]:函数
  • [object Date]:日期
  • [object RegExp]:正则
  • [object Error]:错误
Object.prototype.toString.call(undefined); // [object Undefined]
Object.prototype.toString.call(null); // [object Null]
Object.prototype.toString.call("这是字符串"); // [object String]
Object.prototype.toString.call(1); // [object Number]
Object.prototype.toString.call(true); // [object Boolean]
Object.prototype.toString.call({}); // [object Object]
Object.prototype.toString.call([]); // [object Array]
Object.prototype.toString.call(new Function()); // [object Function]
Object.prototype.toString.call(new Date()); // [object Date]
Object.prototype.toString.call(new RegExp()); // [object RegExp]
Object.prototype.toString.call(new Error()); // [object Error]
总结与对比
  • typeof使用简单,可是只适用于判断基础类型数据
  • instanceof能判断引用类型,不能检测出基本类型,且不能跨iframe使用
  • constructor基本能判断全部类型,除了nullundefined,可是constructor容易被修改,也不能跨iframe使用
  • toString能判断全部类型,所以可将其封装成一个全能的DataType()判断全部数据类型
function DataType(tgt, type) {
    const dataType = Object.prototype.toString.call(tgt).replace(/\[object (\w+)\]/, "$1").toLowerCase();
    return type ? dataType === type : dataType;
}

DataType("young"); // "string"
DataType(20190214); // "number"
DataType(true); // "boolean"
DataType([], "array"); // true
DataType({}, "array"); // false

总结

JS的四种判断方法都有各自的优势跟缺点,要根据具体状况采起合适的判断方式。那么就到这里啦,有什么写得不对还麻烦各位大佬指出。有大家的支持我还会继续写出更好的文章~

结语

❤️关注+点赞+收藏+评论+转发❤️,原创不易,鼓励笔者创做更好的文章

关注公众号IQ前端,一个专一于CSS/JS开发技巧的前端公众号,更多前端小干货等着你喔

  • 关注后回复关键词免费领取视频教程
  • 关注后添加我微信拉你进技术交流群
  • 欢迎关注IQ前端,更多CSS/JS开发技巧只在公众号推送

相关文章
相关标签/搜索