JS 数据类型方面的蹊跷

如今去作前端面试题都是心虚的, 原本能够作对的题,想一想好像有坑,而后答错了。举个例子:javascript

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

因此本文将尽量多的去试图挖一下 javascript 数据类型方面的蹊跷。前端

数据相等

等于问题

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

0 == '0';   // true
0 == [];    // true
'0' == [];  // false
复制代码

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

我的猜想转换的顺序 可能 以下:es6

undefined  < null < Boolean < Number < String < Array
复制代码

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

'0' == true  // false
// 实则是 0 == true 的比较
复制代码

再好比web

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)
复制代码

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
复制代码

说实话,除了几个特例,用这个来判原型其实并非很好的方法。 参考:www.ibm.com/developerwo…

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
复制代码

对象式转换

对象式转换一览

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 库,不妨也分享出来一块儿看看吧。

相关文章
相关标签/搜索