对于前端开发人员来说,可能问起 JS 数据类型,大家觉得都很简单,不就 七种 类型嘛: string, number, boolean, null, undefined, symbol, object。这个大家是都知道的,但是可能问的详细一些,有些地方咱们可能就不太清楚了。
JS 中的数据类型
JS 中共有七种内置的数据类型,分为 基本类型 和 对象类型
基本类型
基本类型共有六种:
- string: 字符串
- numberr: 数字
- boolean: 布尔值
- symbol: 符号
- null: 空值
- undefined:未定义
注意:
- string, number, boolean, null, undefined 这五种类型称为 原始类型(Primitive), 表示不能再细分下去的类型。
- symbol 是 ES6 中新增的数据类型,它是独一无二的值,通过 Symbol 函数调用生成,它生成的值永远不会相等, 由于生成的 symbol 值为原始类型,并且 Symbol 函数不是一个构造器,所以不能通过 new Symbol() 的方式生成,还会报错 ‘Uncaught TypeError: Symbol is not a constructor’。
- null 和 undefined 是两个特殊的类型,他们的值唯一,就是其本身
对象类型
对象类型又叫引用类型,array 和 function 是对象的子类型。对象类型和基本类型不同的是他们的值存储的是引用地址(内存地址),不是真正的值,所以对象类型的值是可变的。
JS 弱类型语言
JS 为什么是 弱类型语言呢?是因为 JS 变量声明的时候不需要预先确定类型,值的类型就是变量的类型,值的类型变了,变量的类型就跟着变了。这就是说上一秒钟是 string 类型,下一秒钟就可能变成 number 类型或 boolean 类型或其他类型,这一过程中发生了强制类型转换。虽然 JS 的这种弱类型语言 不需要预先确定类型的特性给我们带来了便利,但是也会因为类型转换带来一些烦恼,所以必须要掌握类型转换的原理。
JS 的强制类型转换
JS 的强制类型转换规则主要就是一下三种:
- Number 运算符转换
- String 运算符转换
- Boolean 运算符转换
1. Number 运算符转换规则:
- null 转换为 0
- undefined 转换为 NaN
- true 转换为 1, false 转换为 0
- 字符串转换时遵循 数字常量规则,转换失败返回 NaN
- 字符串包含数值字符,包括数值前面带加、减号的情况,则转换为一个十进制数值:Number('11')返回11,Number('+11')返回 11, Number('-11')返回 -11, Number('011') 返回 11 (注意忽略了前面的 零);
- 字符串包含有效的浮点数值,则会转换为相应的 浮点数值(同样,忽略前面的 零);
- 如果字符串包含有效的十六进制格式,如:'0xa', 则会转换为与该十六进制对应的十进制数值;
- 如果字符串包含有效的八进制格式,如:'0o11', 则会转换为与该八进制对应的十进制数值;
- 如果是空字符串,则转换为 0;
- 如果字符串包含除上述情况之外的其他字符,则转换为 NaN, 注意忽略空格,如:Number(' 22 ') 返回 22。
- 对象类型转换时要先转换为 原始值,调用 ToPimitive 转换,请看下文的 ToPimitive
2. String 运算符转换:
- null 转换为 'null'
- undefined 转换为 'undefined'
- true 转换为 'true', false 转换为 'false'
- 数字转换时遵循通用规则,极大极小的数字使用指数形式
- 对象类型转换时同样要先转换为 原始值,调用 ToPimitive 转换,请看下文的 ToPimitive
String(null) // 'null'
String(undefined) // 'undefined'
String(true) // 'true'
String(1) // '1'
String(-1) // '-1'
String(0) // '0'
String(-0) // '0'
String(Math.pow(1000,10)) // '1e+30'
String(Infinity) // 'Infinity'
String(-Infinity) // '-Infinity'
String({}) //'[object Object]'
String([1,[2,3]]) //'1,2,3'
String(['koala',1]) // 'loala,1'
3. Boolean 运算符转换:
除了一下几种转换为 false, 其他的全部为 true
-
null
-
undefined
-
'' (空字符串)
-
0 | +0 | -0 | NaN
这些值以外的其他值,包括 空对象,空数组,转换结果都是 true,甚至连 false 对应的布尔对象 new Boolean(false) 也是 true
Boolean(undefined) // false
Boolean(null) // false
Boolean(0) // false
Boolean(NaN) // false
Boolean('') // false
Boolean({}) // true
Boolean([]) // true
Boolean(new Boolean(false)) // true
ToPimitive(转换为原始值)
ToPimitive 只转换 对象类型(引用类型)的数据,因为 基本类型的数据不需要进行转换。ToPimitive 转换时接收两个参数,第一个参数是要转换的对象,第二个参数是要将改对象转换为 哪种 基本数据类型,第二个参数可以不给,会根据具体对象使用对应的默认值(Date对象转换时默认转换成 string 类型,其他的对象默认 转换成 number 类型)。
/**
* @obj 需要转换的对象
* @type 期望转换为的原始数据类型,可选
*/
ToPrimitive(obj,type)
type 不同值的说明:
- type 为 string:
- 先调用 obj 的 toString 方法,如果为原始值,则return ,否则进行第二步;
- 调用 obj 的 valueOf 方法,如果为原始值,则 return, 否则抛出 Type Error 异常;
- type 为 number:
- 先调用 obj 的 valueOf 方法, 如果为原始值,则return ,否则进行第二步;
- 调用 obj 的 toString 方法,如果为原始值,则 return, 否则抛出 Type Error 异常;
- type 参数为空:
- 若对象为 Date, 则 type 被设置为 string;
- 其他对象,type 被设置为 number
toString
Object.prototype.toString()
toString() 方法返回一个表示对象的字符串。每个对象都有 toString() 方法,单对象被表示为文本值或当以期望字符串的方式引用对象时,该方法会自动调用。
valueOf
Object.prototype.valueOf()
valueOf() 放回指定对象的原始值。不同内置对象的valueOf实现:
- String => 返回字符串值
- Number => 返回数字值
- Date => 返回一个数字,即时间戳,字符串中内容是依赖于具体实现的
- Boolean => 返回Boolean的this值(true / false)
- Object => 返回this
var str = new String('123');
console.log(str.valueOf()); //123
var num = new Number(123);
console.log(num.valueOf()); //123
var date = new Date();
console.log(date.valueOf()); //1526990889729
var bool = new Boolean('123');
console.log(bool.valueOf()); //true
var obj = new Object({valueOf:()=>{
return 1
}})
console.log(obj.valueOf()); //1
JS 转换规则不同场景的应用
自动转换为字符串
- 没有对象的前提下: 主要发生在字符串的 加法运算 时,字符串和 非字符串相加,后者转为 字符串
'2' + 1 // '21'
'2' + true // "2true"
'2' + false // "2false"
'2' + undefined // "2undefined"
'2' + null // "2null"
- 当有对象且与对象+时候
//toString的对象
var obj2 = {
toString:function(){
return 'a'
}
}
console.log('2'+obj2)
console.log(2 + obj2)
//输出结果都是 2a
//常规对象
var obj1 = {
a:1,
b:2
}
console.log('2' + obj1)
console.log(2 + obj1)
//输出结果都是 2[object Object]
//几种特殊对象
'2' + {} // "2[object Object]"
'2' + [] // "2"
'2' + function (){} // "2function (){}"
'2' + ['koala',1] // 2koala,1
注意: '2'+obj2 和 '2'+obj1 中,由于 obj1, obj2 转换时没有指定类型,所以 type 值被指定为了默认值 number,然后按照上面的规则转换
自动转换为Number类型
- 有加法运算,但无 string 类型时,优先转换为 number 类型
true + 0 // 1
true + true // 2
true + false //1
- 除了加法运算,其他的运算符都会自动转换成 number 类型
'5' - '2' // 3
'5' * '2' // 10
true - 1 // 0
false - 1 // -1
'1' - 1 // 0
'5' * [] // 0
false / '5' // 0
'abc' - 1 // NaN
null + 1 // 1
undefined + 1 // NaN
//一元运算符(注意点)
+'abc' // NaN
-'abc' // NaN
+true // 1
-false // 0
注意 一元运算符 是将 值转换成 number 类型的数值
- 抽象相等 == 使用 抽象相等 (双等号)时也是优先 转换为 number 类型
- 如果x,y均为number,直接比较值是否相等
1 == 2 //false
- 如果存在对象,ToPrimitive() type为number进行转换,再进行后面比较
var obj1 = {
valueOf:function(){
return '1'
}
}
1 == obj1 //true
//obj1转为原始值,调用obj1.valueOf()
//返回原始值'1'
//'1'toNumber得到 1 然后比较 1 == 1
[] == ![] //true
//[]作为对象ToPrimitive得到 ''
//![]作为boolean转换得到0
//'' == 0
//转换为 0==0 //true
- 存在boolean,按照ToNumber将boolean转换为1或者0,再进行后面比较
//boolean 先转成number,按照上面的规则得到1
//3 == 1 false
//0 == 0 true
3 == true // false
'0' == false //true
- 如果x为string,y为number,x转成number进行比较
//'0' toNumber()得到 0
//0 == 0 true
'0' == 0 //true
转换为 布尔 类型
- 布尔比较时
- if(), while() 等判断 或者 三元运算符 都要转换成 布尔值
if ( !undefined
&& !null
&& !0
&& !NaN
&& !''
) {
console.log('true');
} // true
//下面两种情况也会转成布尔类型
expression ? true : false
!! expression
JS中的数据类型判断
知道了 JS 中的数据类型及类型转换的规则了,那么如何来判断数据类型呢?
通常有三种方式:typeof, instanceof, Object.prototype.toString()
- typeof typeof 操作符可以判断一个值属于那种基本数据类型,它的返回值一定是一个字符串,值通常是这几项:sting, number, boolean, null, undefined, symbol, object, function,
typeof 'seymoe' // 'string'
typeof true // 'boolean'
typeof 10 // 'number'
typeof Symbol() // 'symbol'
typeof null // 'object' 无法判定是否为 null
typeof undefined // 'undefined'
typeof {} // 'object'
typeof [] // 'object'
typeof(() => {}) // 'function'
上面的代码的输出结果可以看出:
- null 的判断是有误的,这是一个历史遗留问题,知道这个结果就可以。
- typeof 操作符对于 对象类型及其子类型的判断,处理函数都会得到 'object' 的结果,所以要判断一个对象类型的值是 数组[] 或者对象{} 时, 不能得到想要的答案
- instanceof instanceof 操作符 也可以 判断对象的类型,其原理是 检测 构造函数的 prototype 是否在 被检测的对象的原型链上。
[] instanceof Array // true
({}) instanceof Object // true
(()=>{}) instanceof Function // true
但是 instanceof 在判断一个值 是不是一个 对象(Object)时,可能有些问题:
let arr = []
let obj = {}
arr instanceof Array // true
arr instanceof Object // true
obj instanceof Object // true
上面的代码中 Array 的实例 arr 的原型链上也有 Object,arr instanceof Object 也是 true,这是因为 Array 是 Object 的一个子类型。
- Object.prototype.toString() Object.prototype.toString() 可以说是 判断 JS 数据类型的中级解决方法了,用法请看:
Object.prototype.toString.call({}) // '[object Object]'
Object.prototype.toString.call([]) // '[object Array]'
Object.prototype.toString.call(() => {}) // '[object Function]'
Object.prototype.toString.call('seymoe') // '[object String]'
Object.prototype.toString.call(1) // '[object Number]'
Object.prototype.toString.call(true) // '[object Boolean]'
Object.prototype.toString.call(Symbol()) // '[object Symbol]'
Object.prototype.toString.call(null) // '[object Null]'
Object.prototype.toString.call(undefined) // '[object Undefined]'
Object.prototype.toString.call(new Date()) // '[object Date]'
Object.prototype.toString.call(Math) // '[object Math]'
Object.prototype.toString.call(new Set()) // '[object Set]'
Object.prototype.toString.call(new WeakSet()) // '[object WeakSet]'
Object.prototype.toString.call(new Map()) // '[object Map]'
Object.prototype.toString.call(new WeakMap()) // '[object WeakMap]'
可以看出,任何类型的值都能返回正确的数据类型。有几点需要理解:
- 该方法本质就是依托Object.prototype.toString() 方法得到对象内部属性 [[Class]]
- 传入原始类型却能够判定出结果是因为对值进行了包装
- null 和 undefined 能够输出结果是内部实现有做处理
其他
parseInt & parseFloat
在字符串转 number 类型时,除了 Number() 函数外,还有 parseInt() 、parseFloat() 函数,下面具体说一下异同点:
parseInt()函数
parseInt() 函数更专注于字符串是否包含数值模式,parseInt() 函数也会忽略空格,从第一个非空格字符开始转换,如果第一个字符不是数值字符,加号、减号,则会返回 NaN。注意空字符串也返回 NaN(这里跟Number() 函数不同)。如果第一个字符是数值字符、加减号,则会向后依次检测每个字符,知道碰到非数值字符,或字符串末尾。如:parseInt('123abc') 返回 123。
如果字符串以 “0x” 开头,则会被解释为 16进制整数,并将其转换为 十进制 数值。现在已 “0o” 开头的字符串都被转为 0 ,之前被解释为了 八进制。
let num1 = parseInt("1234blue"); // 1234
let num2 = parseInt(""); // NaN
let num3 = parseInt("0xA"); // 10,解释为十六进制整数
let num4 = parseInt("0o23") // 0
此外,parseInt() 函数还可以传入第二个参数,用于指定底数(进制数),也就是用什么进制解析当前字符串。如解析一个16进制的数值字符串:
let num = parseInt('0xaf', 16); // 175
事实上,如果传了第二个表示进制的参数 16, 那么 'ox' 是可以省略的:
let num = ParseInt('af, 16); // 175
通过第二个参数,可以极大扩展目标字符串的解析进制数:
let num1 = parseInt("10",2); // 2,按二进制解析
let num2 = parseInt("10",8); // 8,按八进制解析
let num3 = parseInt("10",10); // 10,按十进制解析
let num4 = parseInt("10",16); // 16,按十六进制解析
因为不传第二个参数,表示让parseInt()函数自己决定如何解析,多数情况下,默认按十进制解析,为了避免出错,建议始终传入第二个参数。
parseFloat()函数
parseFloat()函数的工作方式跟parseInt()函数类似,不同的是
- 第一次出现的小数点是有效的(注意只有第一个小数点有效,后面的无效从而不在解析),如:
parseFloat('123.4.5'); // 123.4; - parseFloat() 函数只解析十进制值,不用传第二个参数;
- 如果字符串表示整数:即没有小数点或者小数点后面只有一个 0 ,则返回整数
let num1 = parseFloat("1234blue"); // 1234,按整数解析
let num2 = parseFloat("0xA"); // 0
let num3 = parseFloat("22.5"); // 22.5
let num4 = parseFloat("22.34.5"); // 22.34
let num5 = parseFloat("0908.5"); // 908.5
let num6 = parseFloat("3.125e7"); //31250000
NaN 相关
NaN 是一个全局对象属性,同时它是一个特殊的 number 类型值
typeof NaN
"number"
那么什么时候返回 NaN 呢
- 非数字字符串解析成数字;
- 算数运算符 与 不是数字的 或者 不能转成数字的 值一起使用;
- 无穷大除以无穷大;
- 给任意负数做开方运算;
Infinity / Infinity; // 无穷大除以无穷大
Math.sqrt(-1); // 给任意负数做开方运算
'a' - 1; // 算数运算符与不是数字或无法转换为数字的操作数一起使用
'a' * 1;
'a' / 1;
parseInt('a'); // 字符串解析成数字
parseFloat('a');
Number('a'); //NaN
'abc' - 1 // NaN
undefined + 1 // NaN
//一元运算符(注意点)
+'abc' // NaN
-'abc' // NaN
关于toString和String
- toString
- toSting() 方法可以将数据转成 字符串, 但是 null 和 undefined 不可以转换。
null.toString()
// Uncaught TypeError: Cannot read property 'toString' of null
undefined.toString()
// Uncaught TypeError: Cannot read property 'toString' of null
- toString() 可以传递参数 -- 数字,代表进制,表示要转换成多少进制的值对应的字符串
二进制:.toString(2);
八进制:.toString(8);
十进制:.toString(10);
十六进制:.toString(16);
- String 如果值有toString()方法,则调用该方法(不传参数)并返回结果。
String 可以将 null 和 undefined 转换成字符串 "null" 和 "undefined",但是不能转换进制
String(null)
// "null"
String(undefined)
// "undefined"