JS做类型检测到底有几种方法?看完本文就知道了!
JS有很多数据类型,对于不同数据类型的识别和相互转换也是面试中的一个常考点,本文主要讲的就是类型转换和类型检测。
数据类型
JS中的数据类型主要分为两大类:原始类型(值类型)和引用类型。常见的数据类型如下图所示:
原始数据类型存在栈中,引用类型在栈中存的是一个引用地址,这个地址指向的是堆中的一个数据对象。需要注意的是null
在这里我们算在原始类型里面,但是你用typeof
的时候会发现他是object
,原因是就算他是一个对象,那他应该在栈中存一个引用地址,但是他是一个空对象,所以这个地址为空,也就是不对应堆中的任意一个数据,他在堆中没有数据,只存在于栈中,所以这里算为了原始类型。引用类型其实主要就是Object
,Array
和Function
这些其实也都是Object
派生出来的。关于这两种类型在内存中的更详细的知识可以看这篇文章。
下面我们来看看这两种类型的区别:
原始类型
原始类型的值无法更改,要更改只能重新赋值。像下面这样尝试去修改是不行的,但是整个重新赋值可以。
原始类型的比较就是比较值,值相等,他们就相等
引用类型
引用类型的值是可以修改的,注意这个时候我们虽然修改了
a
里面的属性,但是a
在栈上的引用地址并没有变化,变化的是堆中的数据。引用类型的比较是比较他们的索引地址,而不是他们的值。比如下面两个对象,看着是一样的,但是他们的引用地址不一样,其实是不等的:
要想让他们相等,得直接将
b
赋值为a
,这样他们的引用地址一样,就是相等的。
类型转换
JS中当不同类型的数据进行计算的时候会进行类型转换,比如下面的例子:
上面的例子中,我们用了加减来操作几个非数字的类型,这时候JS会进行隐式的类型转换,然后再进行加减运算。除了JS本身的隐式转换外,有时候我们还会主动进行类型转换,这就算是显示类型转换了。
隐式类型转换
转为字符串
经常出现在+
运算中,并且其中有一个操作数不是数值类型
let s = 4 + 'px' + 5;
console.log(s); // 4px5
s = 123e-2 + 'a';
console.log(s); // 1.23a
转为数值
经常出现在数学运算中,表示连接字符串的+
运算除外
let s = 'abc';
console.log(+s, -s); // NaN, NaN
s = ' 123 ';
console.log(+s, -s); // 123 -123
s = new Date();
console.log(+s, -s); // 1588675647421 -1588675647421 (这个操作相当于取毫秒数)
转为bool的场景
经常出现在if或者逻辑运算中
let s = 'abc';
if(s) {
console.log(s); // abc
}
console.log(!!s); // true
下面的值在进行bool转换时会转换为false
,除此以外都是true
:
- 0
- NaN
- ''(空字符串)
- null
- undefined
==运算符
当我们使用==
进行比较时,如果两边的类型不同,JS会进行类型转换,然后再比较,===
则不会进行类型转换,如果===
两边的数据类型不同,直接返回false
。
上面只是列举了其中几种情况,更多的情况可以参考下面这种表,这个表来自于MDN。这个表的内容比较多,有些是规范直接定义的,比如null == undefined
,也没有太多逻辑可言。我们不确定时可以来查下这个表,但是实际开发中其实是不建议使用==
的,因为如果你把这个转换关系记错了的话可能就会引入比较难排查的bug,一般推荐直接使用===
。
转换规则
下面这几张表是一些转换规则,来自于《JS权威指南》:
显式类型转换
显式类型转换是我们自己写代码明确转换的类型,可以使代码看起来更清晰,是实际开发时推荐的做法。
转字符串
显式转换为字符串可以使用toString
方法,它的执行结果通常和String()
方法一致。Number类型的toString
方法还支持参数,可以指定需要转换的进制。下面的图是一些原始类型的toString()
,null
和undefined
没有toString
方法,调用会报错:
Number类型的toString
方法支持进制:
转数值
转为数值就很简单了,经常在用,就是这两个全局方法:parseInt
和parseFloat
。
对象转字符串
对象转换为字符串和数值会稍微麻烦点,下面我们单独来探究下。对象转为字符串主要有三种方法:
value.toString()
这个前面讲过了
'' + value
。这个是前面提到过的隐式转换,但是value
是对象的话会按照下面的顺序进行转换:- 先调用
value.valueOf
方法,如果值是原始值,则返回 - 否则调用
value.toString
方法,如果值是原始值,则返回 - 否则报错TypeError
- 先调用
String(value)
。这个是前面提到的显式转换,流程跟前面类似,但是调用toString
和valueOf
的顺序不一样。- 先调用
value.toString
方法,如果值是原始值,则返回 - 否则调用
value.valueOf
方法,如果值是原始值,则返回 - 否则报错TypeError
- 先调用
需要注意的是,Date
对象有点特殊,他始终调用toString
方法。
下面我们写一段代码来验证下:
Object.prototype.valueOf = function() {
return 'aaa';
}
Object.prototype.toString = function() {
return 'bbb';
}
let a = {};
let b = '' + a;
let c = String(a);
console.log(b);
console.log(c);
上述代码输出是,跟我们预期一样:
对象转数值
对象类型转为数值主要有两种方法:
+value
Number(value)
这两种的执行逻辑是一样的:
- 先调用
valueOf
方法,如果值是原始值,就返回 - 否则,调用
toString
方法,然后将toString
的返回值转换为数值
照例写个例子看下:
Object.prototype.valueOf = function() {
return {};
}
Object.prototype.toString = function() {
return 'bbb';
}
let a = {};
let b = +a;
let c = Number(a);
console.log(b);
console.log(c);
上述代码的输出都是NaN
,这是因为我们toString
方法返回的bbb
没办法转化为正常数值,强行转就是NaN
:
类型检测
类型检测是我们经常遇到的问题,面试时也经常问到各种类型检测的方法,下面是几种常用的类型检测的方法。
typeof
做类型检测最常用的就是typeof
了:
let a;
typeof a; // undefined
let b = true;
typeof b; // boolean
let c = 123;
typeof c; // number
let d = 'abc';
typeof d; // string
let e = () => {};
typeof e; // function
let f = {};
typeof f; // object
let g = Symbol();
typeof g; // symbol
instanceof
typeof
最简单,但是他只能判断基本的类型,如果是对象的话,没法判断具体是哪个对象。instanceof
可以检测一个对象是不是某个类的实例,这种检测其实基于面向对象和原型链的,更多关于instanceof原理的可以看这篇文章。下面来看个例子:
let a = new Date();
a instanceof Date; // true
constructor
constructor
的原理其实跟前面的instanceof
有点像,也是基于面向对象和原型链的。一个对象如果是一个类的实例的话,那他原型上的constructor
其实也就指向了这个类,我们可以通过判断他的constructor
来判断他是不是某个类的实例。具体的原理在前面提到的文章也有详细说明。还是用上面那个例子:
let a = new Date();
a.constructor === Date; // true
使用constructor
判断的时候要注意,如果原型上的constructor
被修改了,这种检测可能就失效了,比如:
function a() {}
a.prototype = {
x: 1
}
let b = new a();
b.constructor === a; // 注意这时候是 false
上面为false
的原因是,constructor
这个属性其实是挂在a.prototype
下面的,我们在给a.prototype
赋值的时候其实覆盖了之前的整个prototype
,也覆盖了a.prototype.constructor
,这时候他其实压根就没有这个属性,如果我们非要访问这个属性,只能去原型链上找,这时候会找到Object
:
要避免这个问题,我们在给原型添加属性时,最好不要整个覆盖,而是只添加我们需要的属性,上面的改为:
a.prototype.x = 1;
如果一定要整个覆盖,记得把constructor
加回来:
a.prototype = {
constructor: a,
x: 1
}
duck-typing
duck-typing
翻译叫“鸭子类型”,名字比较奇怪,意思是指一个动物,如果看起来像鸭子,走起路来像鸭子,叫起来也像鸭子,那我们就认为他是只鸭子。就是说我们通过他的外观和行为来判断他是不是鸭子,而不是准确的去检测他的基因是不是鸭子。这种方式在科学上当然是不严谨的,但是在部分场景下却是有效的。用编程语言来说,就是看某个对象是不是具有某些特定的属性和方法,来确定他是不是我们要的对象。比如有些开源库判断一个对象是不是数组会有下面的写法:
function isArray(object) {
return object !== null &&
typeof object === 'object' &&
'splice' in object &&
'join' in object
}
isArray([]); // true
这就是通过检测目标对象是不是包含Array应该有的方法来判断他是不是一个Array。这就是所谓的看着像鸭子,那就是鸭子。但是一个具有splice
和join
方法的对象也能通过这个检测,所以这样是不准确的,只是部分场景适用。
Object.prototype.toString.call
Object.prototype.toString.call
是比较准确的,可以用来判断原生对象具体是哪个类型:
Object.prototype.toString.call(new Array()); // [object Array]
Object.prototype.toString.call(new Date()); // [object Date]
这个方法返回的是[object XXX]
,这个XXX是对应的构造函数名字。但是他只能检测原生对象,对于自定义类型是没有用的:
function a() {}
let b = new a();
Object.prototype.toString.call(b); // [object Object]
可以看到对于自定义类a
的实例b
,我们得到仍然是[object Object]
,而不是我们预期的[object a]
。
一些原生方法: Array.isArray,Number.isInteger
JS为了解决类型检测的问题,也引入了一些原生方法来提供支持,比如Array.isArray
和Number.isInteger
等。Array.isArray
可以用来检测一个对象是不是数组:
Array.isArray([]); // true
Array.isArray(123); // false
Number.isInteger
可以用来检测一个对象是不是整数:
Number.isInteger(1); // true
Number.isInteger(-1); // true
Number.isInteger(-1.1); // false
Number.isInteger('aaa'); // false
如果有原生检测的方法我们当然推荐使用原生方法了,但是目前原生方法并没有那么多和全面,很多时候还是要用前面的方法来检测类型。
小节
JS其实没有一种完美的方法来检测所有的类型,具体的检测方法需要我们根据实际情况来进行选择和取舍。下面是几种方法的总结:
总结
- JS有两种数据类型,原始类型和引用类型,引用类型主要就是对象。
- 当我们使用
+
,逻辑判断或者==
时会有隐式的类型转换。 - 有时候隐式的类型转换会出现我们不想要的结果,如果我们确定要进行判断或者类型转换,最好使用显式的,比如使用
===
,而不是==
。 - 对象转为字符串和数值可能需要调
valueOf
和toString
方法,调用顺序需要看具体场景。 - JS没有一个完美的类型检测方法,我们最好根据需要选择具体的检测方法。
文章的最后,感谢你花费宝贵的时间阅读本文,如果本文给了你一点点帮助或者启发,请不要吝啬你的赞和GitHub小星星,你的支持是作者持续创作的动力。
作者博文GitHub项目地址: https://github.com/dennis-jiang/Front-End-Knowledges
作者掘金文章汇总:https://juejin.im/post/5e3ffc85518825494e2772fd
JS做类型检测到底有几种方法?看完本文就知道了!的更多相关文章
- JavaScript进阶(四)js字符串转换成数字的三种方法
js字符串转换成数字的三种方法 在js读取文本框或者其它表单数据的时候获得的值是字符串类型的,例如两个文本框a和b,如果获得a的value值为11,b的value值为9 ,那么a.value要小于b. ...
- Jsp页面跳转和js控制页面跳转的几种方法
Jsp 页面跳转的几种方法 1. RequestDispatcher.forward() 在服务器端起作用,当使用forward()时,Servlet engine传递HTTP请求从当前的Servle ...
- js 动态加载事件的几种方法总结
本篇文章主要是对js 动态加载事件的几种方法进行了详细的总结介绍,需要的朋友可以过来参考下,希望对大家有所帮助 有些时候需要动态加载javascript事件的一些方法往往我们需要在 JS 中动态添 ...
- 原生JS—实现图片循环切换的两种方法
今天我们主要讲讲如何使用原生JS实现图片的循环切换的方法.多余的话我们就不多说了,我们一个一个开始讲吧. 1 原生JS实现图片循环切换 -- 方法一 在上栗子之前我们先简单介绍一下所用的一些知识点. ...
- js 控制页面跳转的5种方法
js 控制页面跳转的5种方法 编程式导航: 点击跳转路由,称编程式导航,用js编写代码跳转. History是bom中的 History.back是回退一页 Histiory.go(1)前进一页 Hi ...
- js如何动态创建表格(两种方法)
js如何动态创建表格(两种方法) 一.总结 一句话总结: 1.方法一:写好创建表格的html代码,将之赋值给div的innerHTML. 2.方法二.直接用创建好的table元素的方法insertRo ...
- js中常用追加元素的几种方法
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- APP的缓存文件到底应该存在哪?看完这篇文章你应该就自己清楚了
APP的缓存文件到底应该存在哪?看完这篇文章你应该就自己清楚了 彻底理解android中的内部存储与外部存储 存储在内部还是外部 所有的Android设备均有两个文件存储区域:"intern ...
- 【JS】类型检测
本文首发于我的个人博客 : http://cherryblog.site/ 前言 js 中的类型检测也是很重要的一部分,所以说这篇文章我们就来讲一下怎么对 JavaScript 中的基本数据类型进行检 ...
随机推荐
- python2.7安装numpy和pandas
扩展官网安装numpy,use [v][p][n]下载得会比较快 然后在CMD命令行下进入该文件夹然后输入pip install +numpy的路径+文件名.比如我的是:pip install num ...
- stand up meeting 1/14/2016
part 组员 工作 工作耗时/h 明日计划 工作耗时/h UI 冯晓云 主要对生词本卡片的整体设计做修改:协助主程序完成popup部分 ...
- D - Yet Another Monster Killing Problem
题目连接: https://codeforces.com/contest/1257/problem/D 题目大意: n个怪兽,m个英雄,每个怪兽有一定的能力值,每个英雄有一定的能力值和一定的耐力值.耐 ...
- Unity ML-agents 一、初次尝试
前言 曾在高二寒假的时候,跟表哥在外面玩,当时他问我有没有想过以后要做什么,我愣了一下,回答不上来.是的,从没想过以后要做什么,只是一直在完成学校.老师安排的任务,于是那之后半年,我一直在思考,大学要 ...
- [V&N2020 公开赛] Web misc部分题解
0x00 前言 写了一天题目,学到了好多东西, 简单记录一下 0x01 Web HappyCTFd 直接使用网上公开的cve打: 解题思路:先注册一个admin空格账号,注意这里的靶机无法访问外网,邮 ...
- 转:Cookies 和 Session的区别
转自:http://blog.csdn.net/axin66ok/article/details/6175522 1.cookie 是一种发送到客户浏览器的文本串句柄,并保存在客户机硬盘上,可以用来在 ...
- java第八周课后作业
1.系统小练习 package homework; import java.util.Random; import java.util.Scanner; public class Menu { pub ...
- 堆溢出---glibc malloc
成功从来没有捷径.如果你只关注CVE/NVD的动态以及google专家泄露的POC,那你只是一个脚本小子.能够自己写有效POC,那就证明你已经是一名安全专家了.今天我需要复习一下glibc中内存的相关 ...
- Java 多线程实现方式一:继承Thread类
java 通过继承Thread类实现多线程很多简单: 只需要重写run方法即可. 比如我们分三个线程去京东下载三张图片: 1.先写个下载类: 注意导入CommonsIO 包 public class ...
- netcore 下的policy授权自定义返回结果
目前一直在用policy做权限校验,但是好像组里需要将返回结果统一,之前用的都是直接继承AuthorizationHandler然后调用context.Fail(),但是这样会导致没办法自定义返回结果 ...