JS 的数据类型你了解多少?
作为 JavaScript 的⼊⻔级知识点,JS 数据类型在整个 JavaScript 的学习过程中其实尤为重要。因为
在 JavaScript 编程中,经常会遇到边界数据类型条件判断问题,很多代码只有在某种特定的数据
类型下,才能可靠地执⾏。
希望本章的学习,能让你熟练掌握数据类型的判断以及转换等知识,在遇到数据类型判断,隐式转换等问题时,
可以轻松应对。
数据类型概念
Javascript数据类型有下面的8种
其中,前面7种为基础类型,最后 1 种为引用类型,这也是要重点学习的。
因为各种不同的数据类型,创建的对象,最后都存放在内容当中。
因此数据类型大概可以分为两类来进行存储
- 基础类型,存放在栈内存,被引用或者拷贝时,会创建一个完全相等的变量
- 引用类型,存放在堆内容,存储的是地址,多个引用指向同一个地址。也就是说多个地址共享一块内存,这里会涉及到共享的概念
关于引用类型,“共享”的概念,可以通过两段代码来讲解,
片段一: 牛刀小试
1let a = { 2 site: "helloworld.net", 3 name: "兔哥", 4}; 5 6let b = a; 7console.log(a.site); //输出: helloworld.net 8b.site = "csdn.net"; 9 10console.log(a.site); //输出:csdn.net 11console.log(b.site); //输出:csdn.net 12
上面的例子比较简单,有一个对象 a , 又赋值给了 b , 结果就是a, b 是引用类型,并且指向同一块内存
a对象修改,b会受影响 , b对象修改,a会受影响 。
从打印结果就可以看出来:这两个值都指向同一块内存,一个发生了变化 ,另一个也跟着发生了变化 。
其实,对于引用类型来说,变量保存的是地址,并不是真实的值,通过地址,找到内存,才能从内存中读出真实的值。
下面的例子能验证。
片段二:初有小成
1let a = { 2 name: "兔哥", 3 age: 18, 4}; 5 6function change(obj) { 7 obj.age = 20; 8 obj = { 9 name: "张三", 10 age: 30, 11 }; 12 13 return obj; 14} 15 16let b = change(a); 17console.log(b.age); //输出:30 18console.log(a.age); //输出:20 19 20console.log(a); //输出 :{ name: '兔哥', age: 20 } 21console.log(b); //输出 :{ name: '张三', age: 30 } 22
是不是和你想象的有区别?需要注意的是,这里的 function 给我们带来了不一样的东西。
原因如下 :
- 函数传进来的是 obj, 传递的是对象在堆中的内存地址,通过
obj.age = 20;语句,改变了对象a中的age的值 - 接着又把一个新的对象赋值给了
b,此时b就指向了新的一堆内存,和以前的a没有关系了。
结论如下:
-
引用类型的变量,保存的是堆内存中的地址
小知识点:内存中的地址,其实就是一个16进制的数字表示。类似家里的门牌号一样。
-
函数传参数,其实是值传递,会拷贝一分变量的副本,包括引用也是,会拷贝一份引用的副本,只不过这个副本里面保存的还是同一个内存地址
数据类型检测
数据类型也是面试过程中,经常会被遇到的问题。
比如:如何判断是否为数组?
而且在平常写代码过程中,也会经常用到。
很多人只知道用 typeof 来判断,但这样的回答是不能够让面试官满意的。
所以我们要加深对 JS 数据类型的理解,做到对各种数据类型的判断方法了然于胸
再进行归纳总结,这样才能彻底掌握数据类型的判断方法。
第一种判断方法: typeof
这是最常见的一种方法,来一段代码,快速了解用法。
1typeof 10 //‘number’ 2typeof '10' //‘string’ 3typeof undefined //‘undefined’ 4typeof Symbol() //‘symbol’ 5typeof [] //‘object’ 6typeof {} //‘object’ 7typeof console //‘object’ 8typeof console.log //‘object’ 9typeof null //‘object’
最后一种要注意:判断 null ,不要用typeof null , 虽然也会返回 object ,但是这是一个JS的bug
直接用 if( x === null) 判断就行
第二种判断方法:instanceof
我们 new ⼀个对象,那么这个新对象就是它原型链继承上⾯的对象了
通过 instanceof 我们能判断这个对象是否是之前那个构造函数⽣成的对象
这样就基本可以判断出这个新对象的数据类型。下⾯通过代码来了解⼀下。
1let Phone = function () {}; 2let apple = new Phone(); 3 4console.log(apple instanceof Phone); //true 5 6let phone = new String("小米手机"); 7console.log(phone instanceof String); //true 8 9let str = "www.helloworld.net"; 10console.log(str instanceof String); //false
上面就是用instanceof 判断数据类型的大概流程,那么如果让你实现一个instanceof的底层实现,你会怎么写。
请看下面代码:
1function myInstanceof(left, right) { 2 3 // 这里先用typeof来判断基础数据类型,如果是,直接返回false 4 if (typeof left !== "object" || left === null) 5 return false; 6 7 8 // getProtypeOf是Object对象自带的API,能够拿到参数的原型对象 9 let proto = Object.getPrototypeOf(left); 10 while (true) { 11 //循环往下寻找,直到找到相同的原型对象 12 if (proto === null) 13 return false; 14 15 //找到相同原型对象,返回true 16 if (proto === right.prototype) 17 return true; 18 19 proto = Object.getPrototypeof(proto); 20 } 21} 22 23// 验证一下自己实现的myInstanceof是否OK 24console.log(myInstanceof(new Number(100), Number)); // true 25console.log(myInstanceof(100, Number)); // false 26
现在你知道了两种判断数据类型的⽅法,那么它们之间有什么差异呢?
总结了下⾯两点:
- instanceof 可以准确地判断复杂引⽤数据类型,但是不能正确判断基础数据类型;
- ⽽ typeof 也存在弊端,它虽然可以判断基础数据类型(null 除外),但是引⽤数据类型中,除了
function 类型以外,其他的也⽆法判断。
总之,不管单独⽤ typeof 还是 instanceof,都不能满⾜所有场景的需求,⽽只能通过⼆者混写的⽅式
来判断。
但是这种⽅式判断出来的其实也只是⼤多数情况,并且写起来也⽐较难受,你也可以试着写⼀下。
其实我个⼈还是⽐较推荐下⾯的第三种⽅法,相⽐上述两个⽽⾔,能更好地解决数据类型检测问题
第三种判断⽅法:Object.prototype.toString
toString() 是Object的原型⽅法,调⽤该⽅法,可以统⼀返回格式为 [object XXX]的字符串,其中
XXX 就是对象的类型。对于 Object 对象,直接调⽤ toString() 就能返回 [object Object];⽽对于其他对
象,则需要通过 call 来调⽤,才能返回正确的类型信息。我们来看⼀下代码。
1Object.prototype.toString( {} ); // "[object Object]" 2Object.prototype.toString.call( {} ); // 同上结果,加上call也ok 3Object.prototype.toString.call(100); // "[object Number]" 4Object.prototype.toString.call("100"); // "[object String]" 5Object.prototype.toString.call(true); // "[object Boolean]" 6Object.prototype.toString.call(function () {}); // "[object Function]" 7Object.prototype.toString.call(null); //"[object Null]" 8Object.prototype.toString.call(undefined); //"[object Undefined]" 9Object.prototype.toString.call(/123/g); //"[object RegExp]" 10Object.prototype.toString.call(new Date()); //"[object Date]" 11Object.prototype.toString.call([]); //"[object Array]" 12Object.prototype.toString.call(document); //"[object HTMLDocument]" 13Object.prototype.toString.call(window); //"[object Window]"
从上⾯这段代码可以看出,Object.prototype.toString.call() 可以很好地判断引⽤类型,甚⾄可以把
document 和 window 都区分开来。
但是在写判断条件的时候⼀定要注意,使⽤这个⽅法最后返回统⼀字符串格式为 "[object XXX]" ,⽽这
⾥字符串⾥⾯的 "XXX" ,第⼀个⾸字⺟要⼤写(注意:使⽤ typeof 返回的是⼩写),这⾥需要多加留
意。
那么下⾯来实现⼀个全局通⽤的数据类型判断⽅法,来加深你的理解,代码如下。
1function getType(obj) { 2 let type = typeof obj; 3 4 // 先进行typeof判断,如果是基础数据类型,直接返回 5 if (type !== "object") { 6 return type; 7 } 8 9 // 对于typeof返回结果是object的,再进行如下的判断,正则返回结果 10 return Object.prototype.toString 11 .call(obj) 12 .replace(/^\[object (\S+)\]$/, "$1"); 13} 14 15/* 代码验证,需要注意大小写,哪些是typeof判断,哪些是toString判断?思考下 */ 16getType([]); // "Array" typeof []是object,因此toString返回 17getType("100"); // "string" typeof 直接返回 18getType(window); // "Window" toString返回 19getType(null); // "Null"首字母大写,typeof null是object,需toString来判断 20getType(undefined); // "undefined" typeof 直接返回 21getType(); // "undefined" typeof 直接返回 22getType(function () {}); // "function" typeof能判断,因此首字母小写 23getType(/100/g); //"RegExp" toString返回 24
至此,数据类型检测的三种方式介绍完了,也给出了示例代码,希望你可以对比着学习,使用。
下面我们来看下一个问题:数据类型的转换。
数据类型转换
在日常开发中,经常会遇到数据类型的转换问题
有时候需要主动进行强制转换
有时候JS会进行隐式转换,隐式转换的时候就要多留心了。
1"123" == 123; // false or true? 2"" == null; // false or true? 3"" == 0; // false or true? 4[] == 0 // false or true? 5null == undefined; // false or true? 6Number(null); // 返回什么? 7Number(""); // 返回什么? 8parseInt(""); // 返回什么? 9 10{}+10; // 返回什么? 11 12let obj = { 13 [Symbol.toPrimitive]() { 14 return 200; 15 }, 16 valueOf() { 17 return 300; 18 }, 19 toString() { 20 return "Hello"; 21 }, 22}; 23console.log(obj + 200); // 这里打印出来是多少?
上⾯的问题相信你并不陌⽣,基本涵盖了我们平常容易疏漏的⼀些情况,这就是在做数据类型转
换时经常会遇到的强制转换和隐式转换的⽅式,那么下⾯我就围绕数据类型的两种转换⽅式详细讲解
⼀下,希望可以为你提供⼀些借鉴。
强制类型转换
强制类型转换⽅式包括 Number()、parseInt()、parseFloat()、toString()、String()、Boolean(),这⼏
种⽅法都⽐较类似,都是通过⾃身的⽅法来进⾏数据类型的强制转换。
Number() 方法强制转换规则
-
如果是 boolean ,true和false ,分别转成 1 和 0
-
如果是是数字,返回自身
-
如果 null ,返回 0
-
如果是 undefined,返回 NaN
-
如果是字符串,有以下4种规则
- 如果只包含数字,则转为十进制
- 如果包含有效的浮点格式,则转为浮点数值
- 如果是空字符串,则转为 0
- 如果不是以上格式的字符串,则转为NaN
-
如果是Symbol, 抛出错误
-
如果是对象,并且部署了 [Symbol.toPrimitive],那么调用此方法,否则调用对象的valueOf() 方法
然后根据前面的规则返回值,如果转换后的结果是NaN,则调用对象的toString() 方法
再依次按照前面 的转换顺序返回对应的值
下面通过一段代码来说明上面的规则
1Number(true) //1 2Number(false) //0 3Number('0111') //111 4Number(null) //0 5Number('') //0 6Number('abc') //NaN 7Number(-0x11) //-17 8Number(0x11) //17 9
其中,我分别列举了⽐较常⻅的 Number 转换的例⼦,它们都会把对应的⾮数字类型转换成数字类
型,⽽有⼀些实在⽆法转换成数字的,最后只能输出 NaN 的结果。
Boolean() ⽅法的强制转换规则
这个⽅法的规则是:除了 undefined、 null、 false、 ''、 0(包括 +0,-0)、 NaN 转换出来是 false,
其他都是 true。
这个规则很简单,没有那么多规矩
1Boolean(0) //false 2Boolean(null) //false 3Boolean(undefined) //false 4Boolean(NaN) //false 5Boolean(1) //true 6Boolean(13) //true 7Boolean('12') //true
隐式类型转换
凡是通过逻辑运算符 (&&、 ||、 !)、运算符 (+、-、*、/)、关系操作符 (>、 <、 <= 、>=)、相等运算符
(==) 或者 if/while 条件的操作,如果遇到两个数据类型不⼀样的情况,都会出现隐式类型转换。这⾥你
需要重点关注⼀下,因为⽐较隐蔽,特别容易让⼈忽视。
下⾯着重讲解⼀下⽇常⽤得⽐较多的“==”和“+”这两个符号的隐式转换规则
'==' 的隐式类型转换规则
-
如果类型相同,⽆须进⾏类型转换;
-
如果其中⼀个操作值是 null 或者 undefined,那么另⼀个操作符必须为 null 或者 undefined,才会
返回 true,否则都返回 false;
-
如果其中⼀个是 Symbol 类型,那么返回 false;两个操作值如果为 string 和 number 类型,那么就会将字符串转换为 number;
-
如果⼀个操作值是 boolean,那么转换成 number;
-
如果⼀个操作值为 object 且另⼀⽅为 string、number 或者 symbol,就会把 object 转为原始类型再
进⾏判断(调⽤ object 的 valueOf/toString ⽅法进⾏转换)。
如果直接死记这些理论会有点懵,我们还是直接看代码,这样更容易理解⼀些,如下所示。

'+' 的隐式类型转换规则
'+' 号操作符,不仅可以用作数字相加 ,还可以用作字符串拼接。仅当 '+' 两边都是数字时,进行的是加法运算
如果两边都是字符串,无须进行隐式类型转换
除了上面规则,还有一些特殊规则
-
如果其中有⼀个是字符串,另外⼀个是 undefined、null 或布尔型,则调⽤ toString() ⽅法进⾏字符
串拼接;如果是纯对象、数组、正则等,则默认调⽤对象的转换⽅法会存在优先级,然后再进⾏拼接。
-
如果其中有⼀个是数字,另外⼀个是 undefined、null、布尔型或数字,则会将其转换成数字进⾏加
法运算,对象的情况还是参考上⼀条规则
-
如果其中⼀个是字符串、⼀个是数字,则按照字符串规则进⾏拼接。
如果直接死记这些理论会有点懵,我们还是直接看代码,这样更容易理解⼀些,如下所示。
11 + 2 //3 常规情况 2'1' + '2' //'12' 常规情况 3'1' + undefined // '1undefined' , 规则1:undefined 转成字符串 4'1' + null // '1null' ,规则1:null转成字符串 5'1' + true // '1true', 规则1:true转成字符串 6'1' + 2 // '12' , 规则3, 字符串拼接 7
整体来看,如果数据中有字符串,JavaScript 类型转换还是更倾向于转换成字符串,因为第三条规则
中可以看到,在字符串和数字相加的过程中最后返回的还是字符串,这⾥需要关注⼀下。
了解了 '+' 的转换规则后,我们最后再看⼀下 Object 的转换规则。
Object 的转换规则
对象转换的规则,会先调⽤内置的 [ToPrimitive] 函数,其规则逻辑如下:
-
如果部署了 Symbol.toPrimitive ⽅法,优先调⽤再返回;
-
调⽤ valueOf(),如果转换为基础类型,则返回;
-
调⽤ toString(),如果转换为基础类型,则返回;
-
如果都没有返回基础类型,会报错。
直接理解有些晦涩,还是直接来看代码,你也可以在控制台⾃⼰敲⼀遍来加深印象。
1var obj = { 2 value: 1, 3 valueOf() { 4 return 2; 5 }, 6 7 toString() { 8 return "3"; 9 }, 10 11 [Symbol.toPrimitive]() { 12 return 4; 13 }, 14}; 15 16console.log(obj + 1); // 输出: 5 , 因为有Symbol.toPrimitive方法,所以优先调用这个方法,所以结果是 5 17 18 1910 + {} // "10[object Object]" , 调用的是valueOf函数 20 21 22// "1,2,,4,510" , 会先调用valueOf,结果是个数组 23//不是基础类型,然后再调用的是toString函数 24[1,2,undefined,4,5] + 10 25
总结
-
数据类型的基本概念:这是必须掌握的知识点,作为深⼊理解 JavaScript 的基础
-
数据类型的判断⽅法:typeof 和 instanceof,以及 Object.prototype.toString 的判断数据类型、
⼿写 instanceof 代码⽚段,这些是⽇常开发中经常会遇到的,因此你需要好好掌握
- 数据类型的转换⽅式:两种数据类型的转换⽅式,⽇常写代码过程中隐式转换需要多留意,如果
理解不到位,很容易引起在编码过程中的 bug,得到⼀些意想不到的结果。
建议
Javascript语言不像是其它语言一样,Javascript是个残缺的语言,没有开发完就上了。
所以有些犄角旮旯的不符合常理的,我们知道就行了,没必要记住,知道有那回事就行了
但是自己写代码的时候,一定要严格要求自己,一定要注意类型的问题
强制要求自己,按照强类型语言开发要求自己,不要做一些骚操作因为那代表不了技术水平
