JS 数据类型方面的蹊跷

现在去做前端面试题都是心虚的,
本来可以做对的题,想想好像有坑,然后答错了。举个例子:

Number([0]);           // 0
[0] == true;           // false
if ([0]) alert('ok');  // "ok" // 恩? 不都是 false 吗

所以本文将尽可能多的去试图挖一下 javascript 数据类型方面的蹊跷。

  • 数据相等
    • 判等于
    • 判大小
    • -0 问题
  • 数据类型判断
    • if 判断
    • typeof 判断
    • instanceof 判断
    • constructor 判断
    • is 方法判断(如 isNaN())
    • 其他判断
      • Object.is() 方法
      • key in obj 判断
      • prototype 判断
  • 强制数据类型转换
    • 运算时自动转换(如 +'6')
    • 对象式转换(如 Number([1]))
    • 函数式转换
      • 原型函数(如 toString())
      • parse 系列函数(如 parseInt([1]))
      • 参数的隐形转换
      • 其他强行转换转换(如 JSON.stringify())

数据相等

JS 数据类型方面的蹊跷_第1张图片
等于问题

这张图大伙应该很熟悉了,但其实这里面有些很诡异的问题,很迷很迷。

0 == '0';   // true
0 == [];    // true
'0' == [];  // false

双等时也许是进行了类型转换的,
比如都转为数字或字符串后再进行的比较。

个人猜测转换的顺序 可能 如下:

undefined  < null < Boolean < Number < String < Array

它是一层层向下进行转换后进行比较的。

'0' == true  // false
// 实则是 0 == true 的比较

再比如

Boolean([0]);  // true
[0] == true;   // false
// 实际是 '0' == true 最后 0 == true 的比较

<= 这类数值判断,也是类似的,但很快就发现,
以上猜测并不完善,还有更多一步前置的 Number 转换。

2 > true;      // true
'1' > '2';     // false
undefined == undefined;  // true
undefined <= undefined;  // false
// 因为 Number(undefined) 的结果是 NaN

注意 [2] == [2] 当然是为 false 啦,
这个可 别混淆 了,以为也要去转化。

此处稍微提一下 -0 的存在,会造成 Infinity-Infinity 的不同。
但我们多半会做分母不为零的判断的,恩大概会的吧。

0 === -0;           // true
(1/-0) === (1/0);   // false

数据类型判断

if 判断

一般使用 if 大致会有以下五种情况,三目判断并或非 也包含其中。

if (a <= b)
if (a) 
if (a())
if (a = 1)
if (!a)

JS 数据类型方面的蹊跷_第2张图片
if 判断

如图所示,if 中结果即是 Boolean() 转化后的结果。

请再回味一番,切实记住 if 判断与等于判断的不同哟。

还以为 !a 的判断会有坑,试验下来舒了口气,并没有什么特别之处。

typeof 判断

这章好像要记住的和留意的东西也并不多,

typeof [] === 'object';
typeof NaN === 'number'
typeof null === 'object'

却也是判断中稍有点难判的,所以才出现了 Array.isArrayisNaN 这样的方法存在。
为啥我试不出 typeof 为 array 的情况呀,很奇怪耶,是我记错了咩

还有像 Date RegExp arguments 等自然就是对象了,typeof 的坑相对要少很多。

instanceof 判断

[] instanceof Array 判数组真的很方便,但这块也还是有坑的。

'a' instanceof String                // false
(new String('a')) instanceof String  // true

除此之外,还有原型链上的一点问题:

function Foo(){} 
var foo = new Foo(); 
console.log(foo instanceof Foo);  //true

Foo.prototype = new Aoo();
var foo2 = new Foo(); 
console.log(foo2 instanceof Foo)  // true 
console.log(foo2 instanceof Aoo)  // true

说实话,除了几个特例,用这个来判原型其实并不是很好的方法。
参考:https://www.ibm.com/developerworks/cn/web/1306_jiangjj_jsinstanceof/

constructor 判断

constructor 相比 instanceof 有一点优势,就是它不随 __proto__ 的改变

function A(){};
var a = new A();
var b = new A();
a.__proto__ = {};

a instanceof A       // false
b.constructor === A  // true

以往 es5 做继承时还要自己给 A.prototype.constructor 设新值,
有了 es6 的 class 后已经很简单了,用 constructor 来判原型也稳妥了起来。
至于基础数据类型嘛,也不太推荐用此方法。

is 方法判断

isFinite();
isNaN();
Number.isNaN();
Array.isArray();

其他判断

Object.is() 判断

其实 Object.is() 是类似 === 的,但又有点不一样,它是真真正正的绝对相等。

+0 === -0           // true
Object.is(+0, -0)   // false

NaN === NaN          // false
Object.is(NaN, NaN)  // true

key in object 判断

还需稍微分清一下原型与实例即可,即 for-infor-of 的区别。

'0' in [1, 2];          // true
'now' in Date;          // true
'getFullYear' in Date;  // false

至于项目是使用以下哪种判断就见仁见智了。

if (Array.prototype.includes) {}
'includes' in [];

prototype 判断

obj.hasOwnProperty(key)obj.isPrototypeOf(obj2) 等相关方法,整理中

强制数据类型转换

运算式自动转换

+' 014'   // 14
+'0x12'   // 18

1 + '14'    // '114'
1 + '0x12'  // '10x12'
1 + +'14'   // 15
'14' + 1    // '141'

1 + [1, 1];     // '11,1'
1 + {};         // '1[object Object]'

1 + null;       // 1
1  +undefined;  // NaN

很鲜明,当有单独的运算符存在时(单加括号是不行滴),
会帮忙 Number 转换,否则 String 转换。
还请注意上例中后 4 种特殊的情况。

进行 ++ 运算时并不会帮忙转换为数字,还容易报错。
所以使用时这里得留个心眼哟。

++'14'    // ReferenceError

还有两个特立独行的数字运算,即 Infinity0 的正负号。

Infinity+Infinity;      // Infinity
-Infinity+(-Infinity);  // -Infinity
Infinity+(-Infinity);   // NaN

+0+(+0);     // 0
(-0)+(-0);   // -0
(+0)+(-0);   // 0

再看一个绝对不会遇到的特例,
{} + [] 理论上应该是 '[object Object]' + '' 才对,
就算不是也应该是 NaN + 0 吧,结果我又猜错了。
遇事不决问百度,结果震惊了,这里的 {} 被当成空代码块了,+[] 自然就是 0 了。

[] + {}; // '[object Object]'
{} + []; // 0

对象式转换

JS 数据类型方面的蹊跷_第3张图片
对象式转换一览

Number() 转换

NumberparseInt 的不同,将于下文 parseInt 系列方法 讲述

String() 转换

探讨一下 StringtoString 的不同吧。

一方面是部分数据类型没有 toString 方法:

String(null);        // 'null'
(null).toString();        // Uncaught TypeError
(undefined).toString();   // Uncaught TypeError

另一方面是 toString 可以传个进制数的参(仅对数字类型有用

(30).toString(16);    // "1e"
('30').toString(16);  // "30"

至于 Date Error RegRxp 的字符串化,基本不会出啥幺蛾子。

用原型的 toString 来判数据类型也是种很巧妙常用的方法。

function typeOf(obj) {
  var typeStr = Object.prototype.toString.call(obj).split(" ")[1];
  return typeStr.substr(0, typeStr.length - 1).toLowerCase();
}
typeOf([]);    // 'array'

函数式转换

原型方法

toString 在上文已有介绍,但还得再区分一下数组的。

[1,[2,"abc","",0,null,undefined,false,NaN],3].toString();
// "1,2,abc,,0,,,false,NaN,3"

也即是下例想表达的意思:

(null).toString();   // Uncaught TypeError
[null].toString();   // ''

toStringvalueOf 大致是相同的,但是否有不同,整理中...

再则 (1).toFixed Date.parse 等,应该不会有啥常见错误。
只需注意那些是会 对入参进行隐形转换 的,下文 参数的隐形转换 将介绍

parseInt 系列方法

window.parseIntNumber.parseInt 是全等的,即完全相同。

主要来看 NumberparseInt 的不同,挺迷的,
它们并不是单纯的数据类型转化那么简单,举个例子:

Number('');     // 0
parseInt('');   // NaN

parseInt 就很花哨,还会再多进行一些暗箱操作来判断和适配成数字。
可见,用 Number 转非整数时会是更好的选择。

parseInt(' 10 ');   // 10  // 自动去空格,通用
parseInt('10.2');   // 10  // 数字后的全剔除,Number 和 parseFloat 没问题
parseInt('1e2');    // 1   // 区分不出科学计数法,Number 和 parseFloat 没问题
parseFloat('0x5');  // 0   // 区分不出进制,Number 和 parseInt 没问题

当参数为数组时,当然也是先转 String 的咯,
parseInt 又能去除 , 后的字符,所以就有下面的情况。

Number([1, 2]);    // NaN
parseInt([1, 2]);  // 1

参数的隐形转换

比较典型的 isNaN 是先用 Number 转了一次,但 Number.isNaN 就没有。

isNaN('1x');          // true
Number.isNaN('1x');   // false

这方面没做什么整理,遇到了再补吧。

'12'.replace(1, '');    // "2"

其他强行转换

JSON.stringify()

JSON.parse(JSON.strigify()) 深拷贝时可得注意了哟。
其实递归加对象解构来做深拷贝要更好一些哟。

JSON.stringify(Infinity);   // 'null'
JSON.stringify(NaN);        // 'null'
JSON.stringify(undefined);        // undefined
JSON.stringify({a: undefined});   // '{}'
JSON.stringify({a: null});        // '{"a":null}'
JSON.stringify(() => {});         // 'undefined'
encode 系列

encodeURI 方法不会对下列字符编码 ASCII字母、数字、~!@#$&*()=:/,;?+'

encodeURIComponent 方法不会对下列字符编码 ASCII字母、数字、~!*()'

所以 encodeURIComponentencodeURI 编码的范围更大。

其他
Array.from('foo');          // ["f", "o", "o"]
Object.assign([1], [2,3]);  // [2, 3]

大致就是这些了,写完要自闭一会,整个过程充满了怀疑与揣测。
虽然做了较为系统的拆分,但还是得承认没写好,敬请期待后续吧。

我还有一个 BUG 库,不妨也分享出来一起看看吧。

你可能感兴趣的:(JS 数据类型方面的蹊跷)