JS 数据类型方面的蹊跷

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

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

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

数据相等

等于问题

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

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)

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

对象式转换

对象式转换一览

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容