9大数据类型
数字中奇怪的值:
- NaN:不是有效数字,但是是 number 类型。isNaN 是检测值是不是 NaN,在检查的时候,如果当前值不是数字类型,会先隐式转换为数字类型(Number 转换),然后在判断是否为有效数字。Object.is(NaN, NaN) 也可以检测值是否是 NaN
数据类型转换
在前端的类型转换中,分为两种:手动转换和隐式转换
其他类型转换为 Number 类型
- 手动转换
- Number([val])
- parseInt/parseFloat([val])
- 隐式转换(浏览器内部默认是先转换为 Number 在进行计算)
- isNaN([val])
- 数字运算(+、-、*、/、%),但是 + 运算在出现字符串的情况不是数字运算,是字符串拼接
- 在进行 == 比较的时候,有些值需要转换为数字在比较
Number 转换
// 字符串转换为数字 Number('') => 0 Number('10') => 10 Number('10px') => NaN // 只要出现非有效数字结构就是 NaN // 布尔值转换为数字 Number(true) => 1 Number(false) => 0 // 其他类型转换为数组 Number(null) => 0 Number(undefined) => NaN Number(Symbol(1)) => 报错 Number(BigInt(1)) => 1 对象转换为数字 => 先基于 valueOf 方法转换为原始类型,没有原始类型在基于 toString 转换为字符串,在将字符串转换为数字
parseInt/parseFloat 转换
parseInt 转换机制(parseFloat 类似,只是多识别一个小数点):
- 从字符串左侧的第一个字符串开始查找有效数字。
- 遇到非有效数字字符就停止查找,无论后面是否还有有效数字都不会再管。
- 把找到的有效数字字符转换为数字。
- 如果一个有效数字字符都么有找到,结果就是NaN。
看几个某大厂的面试题例子:输出结果是什么?
parseInt(''); Number(''); isNaN(''); parseInt(null); Number(null); isNaN(null); parseInt('1px'); Number('1px'); isNaN('1px'); parseInt('1.1px') + parseFloat('1.1px') + typeof parseInt(null); isNaN(Number(!!Number(parseInt('1.8')))); typeof !parseInf(null) + !isNaN(null); 1 + false + undefined + [] + 'Tencent' + null + true + {};
并且对于 parseInt() 还有一个比较经典的面试题:
let arr = [1,2,3,4]; arr = arr.map(parseInt); console.log(arr);
这里需要先搞清楚 parseInt([value], [radix])的规则:
- [radix] 这个值是一个进制,不写或者写0默认都是按照10进制进行处理(特殊情况:如果 value 是以 0x 开头,则默认值是 10 或者 16)。
- 进制有一个取值的范围,2~36 之间,如果不在这之间,整个程序运行结果一定是 NaN。
- [value] 是 [radix] 进制的值,需要将 [value] 转换为 十进制。
arr.map(parseInt) => parseInt('1', 0); => 1 parseInt('2', 1); => NaN parseInt('3', 2); => NaN parseInt('4', 3); => NaN => [1, NaN, NaN, NaN]
隐式转换
看一个简单的例子:
[] == false;
对象和布尔的比较,都是转换为数字(隐式转换),对象转换为数字先基于 valueOf 获取原始值,如果没有原始值在去 toString 转换为字符串然后在转换为数字。
在上面的例子中:
- [] 基于 valueOf 获取原始值,[].valueOf() => [] ,发现 [] 没有原始值。
- [] 没有原始值就调用 toString 转换为字符串,[].toString() => ''。
- '' 转换为数字为 0 。
- false 转换为数字为 0。
- 所以 [] == false => true
在看一个扩展的例子:
![] == false;
- 这里的比较先看符号优先级,! 的优先级高于 == ,所以是 ![] 的结果在和 false 进行比较。
- ![] 的潜在意思就是将 [] 转换为布尔在取反。
- 在 js 中 ''、0、null、undefined、NaN 这五个值变成布尔为 false ,其他的值都是 true ,所以 [] 转换为布尔结果 为 true。 ![] => false。
- ![] == false 结构就是 true。
对象转换的规则,会先调用内置的 [ToPrimitive] 函数,其规则逻辑如下:
- 如果部署了 Symbol.toPrimitive 方法,优先调用再返回;
- 调用 valueOf(),如果转换为基础类型,则返回;
- 调用 toString(),如果转换为基础类型,则返回;
- 如果都没有返回基础类型,会报错。
其他类型转换为 String 类型
- 手动转换
- String([val])
- toString()
- 隐式转换
- +运算,如果一边出现字符串,则是字符串拼接。
- 这里有一种特殊的情况,如果出现对象的 + (例如:1+[])也会变成字符串拼接,原因在于对象转换为数字的过程中会先将对象转换为字符串,+ 遇到字符串就变成字符串的拼接了。
- 对于 +/++ 这种不一定是字符串拼接,是运算。
- 还有一种特殊情况 {} + 0 这种情况,左边貌似一个对象,但是在处理的时候会当做一个代码块在处理。所以结果是数字 0 。
- 把对象转换为数字,需要先 toString() 转换为字符串,再去转换为数字
其他类型转换为 Boolean 类型
- 手动转换
- ![val]
- !![val]
- Boolean([val])
- 隐式转换
- 在循环或者条件判断中,条件处理的结果就是布尔类型值
数据类型检测
第一种判断方法:typeof
typeof 1 => 'number' typeof '1' => 'string' typeof treu => 'boolean' typeof undefined => undefined typeof null => 'object' typeof Symbol(1) => 'symbol' typeof 1n => 'bigint' typeof [] =>'object' typeof {} =>'object' typeof console => 'object' typeof console.log => 'function'
typeof 的类型检测有两点需要注意:
- typeof null 结果是 'object',一般大家都说这是计算机的一个 Bug 导致的,但是到底是一个什么 Bug 导致了这个问题了?
“typeof null”错误是 JavaScript 第一版的遗留物。在这个版本中,值存储在 32 位单元中,由一个类型标签(1-3 位)和值的实际数据组成。类型标签存储在单元的低位中。其中有五个:
- 000:对象
- 1:整数
- 010:浮点数
- 100:字符串
- 110:布尔值
也就是说,最低的位是一位,那么类型标记只有一位长。或者它是零,那么类型标签的长度是三位,为四种类型提供两个额外的位。有一个值比较特殊:
- undefined 是整数 -2 ^30 (整数范围之外的数字)
- null 是机器码 NULL 指针。一个对象类型标签加上一个为零的引用。 这就很明显为什么 typeof null 结果是 'object' 了。
并且在ECMA6中,曾经有提案为历史平凡,将 type null 的值纠正为 'null',但最后提案被拒了。理由是历史遗留代码太多。
- 引用数据类型 Object,用 typeof 来判断的话,除了 function ,其余都是 'object'。这也是为为什么在类型区分的时候,需要将 object 和 function 区分开的原因之一。
typeof 它虽然可以判断基础数据类型(null 除外),但是引用数据类型中,除了 function 类型以外,其他的也无法判断。
第二种判断方法:instanceof
[value] instanceof [constructor]
instanceof 运算符用来检测 [value] 在其原型链中是否存在一个 [constructor] 的 prototype 属性。
function myInstanceof(left, right) { // 先用 typeof 来判断基础数据类型,如果是,直接返回 false if(typeof left !== 'object' || left === null) return false; // 然后使用 getProtypeOf 获取参数的原型对象 let proto = Object.getPrototypeOf(left); // 循环往下寻找,直到找到相同的原型对象 while(true) { // 找到最顶层 if(proto === null) return false; // 找到相同原型对象,返回true if(proto === right.prototype) return true; // 继续向上寻找 proto = Object.getPrototypeof(proto); } }
instanceof 可以准确地判断复杂引用数据类型,但是不能正确判断基础数据类型;
第三种判断方法:constructor
[value].constructor === 类,相对于 instanceof 来讲基本类型也可以处理,而且因为获取实例的 constructor 实际上获取的是所属的类,所以在检测准确性上比 instanceof 还好一点,但是 constructor 是可以随意被改动的。
第四种判断方法:Object.prototype.toString
toString() 是 Object 的原型方法,调用该方法,可以统一返回格式为 “[object Xxx]” 的字符串,其中 Xxx 就是对象的类型。对于 Object 对象,直接调用 toString() 就能返回 [object Object];而对于其他对象,则需要通过 call 来调用,才能返回正确的类型信息。
Object.prototype.toString({}) => "[object Object]" Object.prototype.toString.call({}) => "[object Object]" Object.prototype.toString.call(1) => "[object Number]" Object.prototype.toString.call('1') => "[object String]" Object.prototype.toString.call(true) =>"[object Boolean]" Object.prototype.toString.call(function(){}) => "[object Function]" Object.prototype.toString.call(null) => "[object Null]" Object.prototype.toString.call(undefined) => "[object Undefined]" Object.prototype.toString.call(/123/g) => "[object RegExp]" Object.prototype.toString.call(new Date()) => "[object Date]" Object.prototype.toString.call([]) => "[object Array]" Object.prototype.toString.call(document) => "[object HTMLDocument]" Object.prototype.toString.call(window) => "[object Window]"
通过 Object.prototype.toString 可以实现一个通过的类型判断方法:
function getType(obj){ let type = typeof obj; // 先进行typeof判断,如果是基础数据类型,直接返回 if (type !== "object") { return type; } // 对于typeof返回结果是object的,再进行如下的判断,正则返回结果 return Object.prototype.toString.call(obj).replace(/^[object (\S+)]$/, '$1'); }
在 ES3 中,在 toString 方法被调用时,会执行下面的操作步骤:
- 获取 this 对象的 [[Class]] 属性的值。
- 计算出三个字符串 "[object ",第一步的操作结果 Result(1),以及 "]" 连接后的新字符串。
- 返回第二步的操作结果 Result(2) .
在 ES5 中,在 toString 方法被调用时,会执行下面的操作步骤:
- 如果 this 的值为 undefined,则返回 "[object Undefined]"。
- 如果 this 的值为 null,则返回 "[object Null]"。
- 获取 ToObject(this) 的结果 Result(1)。
- 获取 Result(1) 的内部属性 [[Class]] 的值 Result(2)。
- 返回三个字符串"[object ", Result(2), 以及 "]"连接后的新字符串.