JS有很多数据类型,对于不同数据类型的识别和相互转换也是面试中的一个常考点,本文主要讲的就是类型转换和类型检测。

数据类型

JS中的数据类型主要分为两大类:原始类型(值类型)和引用类型。常见的数据类型如下图所示:

原始数据类型存在栈中,引用类型在栈中存的是一个引用地址,这个地址指向的是堆中的一个数据对象。需要注意的是null在这里我们算在原始类型里面,但是你用typeof的时候会发现他是object,原因是就算他是一个对象,那他应该在栈中存一个引用地址,但是他是一个空对象,所以这个地址为空,也就是不对应堆中的任意一个数据,他在堆中没有数据,只存在于栈中,所以这里算为了原始类型。引用类型其实主要就是ObjectArrayFunction这些其实也都是Object派生出来的。关于这两种类型在内存中的更详细的知识可以看这篇文章。

下面我们来看看这两种类型的区别:

原始类型

  1. 原始类型的值无法更改,要更改只能重新赋值。像下面这样尝试去修改是不行的,但是整个重新赋值可以。

  2. 原始类型的比较就是比较值,值相等,他们就相等

引用类型

  1. 引用类型的值是可以修改的,注意这个时候我们虽然修改了a里面的属性,但是a在栈上的引用地址并没有变化,变化的是堆中的数据。

  2. 引用类型的比较是比较他们的索引地址,而不是他们的值。比如下面两个对象,看着是一样的,但是他们的引用地址不一样,其实是不等的:

    要想让他们相等,得直接将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:

  1. 0
  2. NaN
  3. ''(空字符串)
  4. null
  5. undefined

==运算符

当我们使用==进行比较时,如果两边的类型不同,JS会进行类型转换,然后再比较,===则不会进行类型转换,如果===两边的数据类型不同,直接返回false

上面只是列举了其中几种情况,更多的情况可以参考下面这种表,这个表来自于MDN。这个表的内容比较多,有些是规范直接定义的,比如null == undefined,也没有太多逻辑可言。我们不确定时可以来查下这个表,但是实际开发中其实是不建议使用==的,因为如果你把这个转换关系记错了的话可能就会引入比较难排查的bug,一般推荐直接使用===

转换规则

下面这几张表是一些转换规则,来自于《JS权威指南》:

显式类型转换

显式类型转换是我们自己写代码明确转换的类型,可以使代码看起来更清晰,是实际开发时推荐的做法。

转字符串

显式转换为字符串可以使用toString方法,它的执行结果通常和String()方法一致。Number类型的toString方法还支持参数,可以指定需要转换的进制。下面的图是一些原始类型的toString()nullundefined没有toString方法,调用会报错:

Number类型的toString方法支持进制:

转数值

转为数值就很简单了,经常在用,就是这两个全局方法:parseIntparseFloat

对象转字符串

对象转换为字符串和数值会稍微麻烦点,下面我们单独来探究下。对象转为字符串主要有三种方法:

  1. value.toString()

    这个前面讲过了

  2. '' + value。这个是前面提到过的隐式转换,但是value是对象的话会按照下面的顺序进行转换:

    1. 先调用value.valueOf方法,如果值是原始值,则返回
    2. 否则调用value.toString方法,如果值是原始值,则返回
    3. 否则报错TypeError
  3. String(value)。这个是前面提到的显式转换,流程跟前面类似,但是调用toStringvalueOf的顺序不一样。

    1. 先调用value.toString方法,如果值是原始值,则返回
    2. 否则调用value.valueOf方法,如果值是原始值,则返回
    3. 否则报错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);

上述代码输出是,跟我们预期一样:

对象转数值

对象类型转为数值主要有两种方法:

  1. +value
  2. Number(value)

这两种的执行逻辑是一样的:

  1. 先调用valueOf方法,如果值是原始值,就返回
  2. 否则,调用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。这就是所谓的看着像鸭子,那就是鸭子。但是一个具有splicejoin方法的对象也能通过这个检测,所以这样是不准确的,只是部分场景适用。

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.isArrayNumber.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其实没有一种完美的方法来检测所有的类型,具体的检测方法需要我们根据实际情况来进行选择和取舍。下面是几种方法的总结:

总结

  1. JS有两种数据类型,原始类型和引用类型,引用类型主要就是对象。
  2. 当我们使用+,逻辑判断或者==时会有隐式的类型转换。
  3. 有时候隐式的类型转换会出现我们不想要的结果,如果我们确定要进行判断或者类型转换,最好使用显式的,比如使用===,而不是==
  4. 对象转为字符串和数值可能需要调valueOftoString方法,调用顺序需要看具体场景。
  5. JS没有一个完美的类型检测方法,我们最好根据需要选择具体的检测方法。

文章的最后,感谢你花费宝贵的时间阅读本文,如果本文给了你一点点帮助或者启发,请不要吝啬你的赞和GitHub小星星,你的支持是作者持续创作的动力。

作者博文GitHub项目地址: https://github.com/dennis-jiang/Front-End-Knowledges

作者掘金文章汇总:https://juejin.im/post/5e3ffc85518825494e2772fd

JS做类型检测到底有几种方法?看完本文就知道了!的更多相关文章

  1. JavaScript进阶(四)js字符串转换成数字的三种方法

    js字符串转换成数字的三种方法 在js读取文本框或者其它表单数据的时候获得的值是字符串类型的,例如两个文本框a和b,如果获得a的value值为11,b的value值为9 ,那么a.value要小于b. ...

  2. Jsp页面跳转和js控制页面跳转的几种方法

    Jsp 页面跳转的几种方法 1. RequestDispatcher.forward() 在服务器端起作用,当使用forward()时,Servlet engine传递HTTP请求从当前的Servle ...

  3. js 动态加载事件的几种方法总结

    本篇文章主要是对js 动态加载事件的几种方法进行了详细的总结介绍,需要的朋友可以过来参考下,希望对大家有所帮助   有些时候需要动态加载javascript事件的一些方法往往我们需要在 JS 中动态添 ...

  4. 原生JS—实现图片循环切换的两种方法

    今天我们主要讲讲如何使用原生JS实现图片的循环切换的方法.多余的话我们就不多说了,我们一个一个开始讲吧. 1  原生JS实现图片循环切换 -- 方法一 在上栗子之前我们先简单介绍一下所用的一些知识点. ...

  5. js 控制页面跳转的5种方法

    js 控制页面跳转的5种方法 编程式导航: 点击跳转路由,称编程式导航,用js编写代码跳转. History是bom中的 History.back是回退一页 Histiory.go(1)前进一页 Hi ...

  6. js如何动态创建表格(两种方法)

    js如何动态创建表格(两种方法) 一.总结 一句话总结: 1.方法一:写好创建表格的html代码,将之赋值给div的innerHTML. 2.方法二.直接用创建好的table元素的方法insertRo ...

  7. js中常用追加元素的几种方法

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  8. APP的缓存文件到底应该存在哪?看完这篇文章你应该就自己清楚了

    APP的缓存文件到底应该存在哪?看完这篇文章你应该就自己清楚了 彻底理解android中的内部存储与外部存储 存储在内部还是外部 所有的Android设备均有两个文件存储区域:"intern ...

  9. 【JS】类型检测

    本文首发于我的个人博客 : http://cherryblog.site/ 前言 js 中的类型检测也是很重要的一部分,所以说这篇文章我们就来讲一下怎么对 JavaScript 中的基本数据类型进行检 ...

随机推荐

  1. python2.7安装numpy和pandas

    扩展官网安装numpy,use [v][p][n]下载得会比较快 然后在CMD命令行下进入该文件夹然后输入pip install +numpy的路径+文件名.比如我的是:pip install num ...

  2. stand up meeting 1/14/2016

    part 组员                工作              工作耗时/h 明日计划 工作耗时/h    UI 冯晓云  主要对生词本卡片的整体设计做修改:协助主程序完成popup部分 ...

  3. D - Yet Another Monster Killing Problem

    题目连接: https://codeforces.com/contest/1257/problem/D 题目大意: n个怪兽,m个英雄,每个怪兽有一定的能力值,每个英雄有一定的能力值和一定的耐力值.耐 ...

  4. Unity ML-agents 一、初次尝试

    前言 曾在高二寒假的时候,跟表哥在外面玩,当时他问我有没有想过以后要做什么,我愣了一下,回答不上来.是的,从没想过以后要做什么,只是一直在完成学校.老师安排的任务,于是那之后半年,我一直在思考,大学要 ...

  5. [V&N2020 公开赛] Web misc部分题解

    0x00 前言 写了一天题目,学到了好多东西, 简单记录一下 0x01 Web HappyCTFd 直接使用网上公开的cve打: 解题思路:先注册一个admin空格账号,注意这里的靶机无法访问外网,邮 ...

  6. 转:Cookies 和 Session的区别

    转自:http://blog.csdn.net/axin66ok/article/details/6175522 1.cookie 是一种发送到客户浏览器的文本串句柄,并保存在客户机硬盘上,可以用来在 ...

  7. java第八周课后作业

    1.系统小练习 package homework; import java.util.Random; import java.util.Scanner; public class Menu { pub ...

  8. 堆溢出---glibc malloc

    成功从来没有捷径.如果你只关注CVE/NVD的动态以及google专家泄露的POC,那你只是一个脚本小子.能够自己写有效POC,那就证明你已经是一名安全专家了.今天我需要复习一下glibc中内存的相关 ...

  9. Java 多线程实现方式一:继承Thread类

    java 通过继承Thread类实现多线程很多简单: 只需要重写run方法即可. 比如我们分三个线程去京东下载三张图片: 1.先写个下载类: 注意导入CommonsIO 包 public class ...

  10. netcore 下的policy授权自定义返回结果

    目前一直在用policy做权限校验,但是好像组里需要将返回结果统一,之前用的都是直接继承AuthorizationHandler然后调用context.Fail(),但是这样会导致没办法自定义返回结果 ...