一篇文章让你轻松记住js的隐式转化
作者:我是小橘子哦
前言
之前写过一篇文章[[js让人诟病的这些feature]]中提出过一个疑问.
这个问题一开始我想简单了.认为只要记住一些特性就可以了.所以直接用穷举法来进行规律的总结.
但是当遇到console.log(Number([]))
的结果是0, 而console.log(Number([1, 2]))
的结果是NaN.都什么乱七八糟的,里面必有蹊跷.虽然能够强背背下来, 但是作为一个有追求的程序员还是要弄明白它是怎么一回事的.
console.log({} - {}) // NaN console.log([] - []) // 0 console.log([] - [1, 2]) // NaN console.log([] == ![]) // true console.log({} == {}) // false
要理解上面打印的结果,就是要理解Number([])
的值,Number([1, 2])
的值, Number({})
的值, 以及Boolean([])
返回的值. 下面慢慢说道.
一、包装类
Boolean()
Boolean只有两种结果,true和false.
- Boolean结果为false的类型,我们通常称他为 falsey, 中文叫虚值. 这些值在 [[[js让人诟病的这些feature]] 有所提及,即,
0、null、undefined、false、''、NaN
有些文章把-0和+0算成两个
上面这些都是原始值转原始值.其他的都是true
如果是引用值转原始值都为true. 还有下面这些一时间想不起来的引用类型.
Boolean(/d/) Boolean(new Error()) console.log(Boolean(Symbol()))
扩展
还有一种容易弄混的typeof 返回结果, node环境中:
console.log(typeof Date()) console.log(typeof new Date()) console.log(Date()) // Thu Jan 13 202l2 22:29:36 GMT+0800 (中国标准时间) console.log(new Date()) // 2022-01-13T14:29:36.660Z
Number()
引用类型转换Number
易错点出现在Number() 上面. 尤其是引用类型转化为原始类型. 了解了这个,开头的例子就能够理解了.
我们只有在知道了Number(引用类型)的规则才能够判断引用类型转化为原始类型的结果什么,不然是不可能判断得出的饿,靠猜走不远.
- 我们假设有如下这么一个对象
const obj = { toString() { return 2 }, valueOf() { return 1 } }
- 我们Number()包裹它
console.log(Number(obj))
打印出的结果是1. 哦,有意思的来了,
consot obj = { toString() { return 2 }, valueOf() { return {} } } console.log(Number(obj)) // 2
让我们让valueOf 返回的值是对象的时候, 打印出 2 , 反之则直接打印出String原始值,再转化为number类型.
一般来说valueof 就是代表值,没有意义,也不用处理. 值是什么就是什么,比如说[1, 2, 3].valueOf() 直接打印就是[1, 2, 3].
我们也可以通过这个方法来解决[[让 a == 1 && a == 2 && a == 3 成立]]的问题.
所以我们很容易得出这么一个规律: 当valueOf 返回值是引用类型的时候, 就去拿toString 返回的值. 展开来说就是:
- 如果valueOf返回原始值,就Number包装之后返回
- 如果valueOf返回的对象,就去toString()方法中找
- 如果toString() 返回原始值,就Number包装之后返回
- 如果toString()返回的是对象,且是自己重写的.那么就直接报错
- 如果不是充血的,那么就调用Obejct.prototye.toString方法
这里显然还涉及到了[[原型链]]的问题,所以说其实隐式转化的问题不是想象中的那么简单的.
而我们创建的对象基本没讲过会创建这两个方法.所以它很显然就是继承至Object上面的方法. 也就是说,我们在研究这个问题的是,就是在研究Object.prototype.toString.call()
返回的值.
console.log(Object.prototype.tostring.call('123')); console.log(Object.prototype.toString.call(123)); console.log(object.prototype.tostring.call(true)); console.log(Object.prototype.tostring.call(undefined)); console.log(Object.prototype.tostring.call(null)); console.log(Object.prototype.tostring.call(function){})); console.log(Object.prototype.toString.call([1,2,3])); console.log(Object.prototype.tostring.call(ff));
打印的结果如下:
[object string] [object Number] [object Boolean] [object Undefined] [object Null] [object Functionl] [object Arrayl] [object objectl]
原始类型转Number
console.log(Number(undefined)) // NaN console.log(Number (null)) // 0 console.log(Number(true)) // 1 console.log(Number (false)) // 0 console.log(Number(NaN)) // NaN console.1og(Number (Infinity))// Infinity console.log(Number('') // 0 console.log(Number(' ') // 0 console.log(Number('123')) // 123
上面没啥好说的, 背下就行. 需要注意的是Number的值,除了我们平时使用的的数字意义之外,还有NaN、Infinity.
还有这些混淆点是需要注意的:
console.log((123).toString()) // 123 console.log(undefined.toString()) // 报错 console.log(nul.toString()) // 报错 复制代码
undefined 和 null 没有包装类, 本身又是基础类型 ,自然没有其他乱码七糟的方法. 所以报错.
扩展
Argument和document
console.log(Object.toString.call(argument)) // [object Argument] console.log(Object.toString.call(document)) // [object HTMLDocument]
HTMLDocument是浏览器给我们提供的对象类型.
由此也可以得知Object.prototype.toString 方法的运用之广, 识别类型之多, 比起typeof 简陋的返回值强大得多. 当然每个都有每个使用的场景就是了.
手写typeof
typeof是jscore自带,而且也不是语法糖. 我一开始看到这个面试题的时候是懵逼的. 难道要手写typeof的引擎代码? 解释一下从js第一个版本就存在的typeof null为object吗?
但是还真有公司考这个, 有点睿智,大聪明. 所以我看了看网上别人对于这的解析... 就是利用Object.prototype.toString.call()
返回的结果,在进行字符串的切割,之后后面那个单词返回出去.
就这? 脱裤子放屁,多此一举.
Number转化非二进制
Number(0xfff) // 4095 Number(070) // 56
Number可以直接识别不同位数转化成十进制.
parseInt和Number关系
Number('123abc') // NaN Number('ad123') // NaN Number(' 123') // 123
而[[parseInt]]就很好的解决了这个问题,它可以说是对于Number()很好的一个补充
parseInt('123abc') // 123 parseInt('123asd1') // 123 parseInt('ad123') // NaN parseInt(' 123') // 123
String()
Object.prototype.toString
对于String()的使用依旧使用Number()使用的例子
const obj = { toString() { return 2 }, valueOf() { return 1 } }
当我触发String(obj)的时候,就和Number()完全相反.
console.log(String(obj)) // 2
直接访问的是toString()方法.
const obj = { toString() { return {} }, valueOf() { return 1 } } console.log(String(obj)) // 1
但是如果toString() 返回的是引用类型的话, 就往valueOf()方法上面找. 可以说和Number()的完全相反,但是也符合情理 .
通过重写toString()和valueOf()的方法来了解内部的运行规则是一种很好的方式.
如果不重写的话,Object.prototype.toString.call(对象), 返回值参看Number()部分的内容.
console.log(String({})) // [object Object]
Array.prototype.toString
这个记忆上没啥好说的, 直接把外面的[]给拆了就行.
console.log(String([1])) // '1' console.log(String([1, 2])) // '1, 2' console.log(Array.prototype.toString.call([1])) // '1' console.log(Array.prototype.toString.call([1, 2])) // '1, 2'
二、隐式转化触发规则
前面说了显示转化的规律. 下面是能够触发隐式转化的规则.
和运算符规则是和[[运算符的优先级]],在这里不提,可自行查阅.
布尔的隐式转化
当出现判断的时候,会出现隐式转化.
if, switch, while, for(; ;), &&, ||, !, !!, ? : 三元
number的隐式转化
只要有小学的知识都知道运算符,它是用于数字之间的计算的.在JavaScript中也是基本是一样的.
+ - * % == ~~ & | ~ ^ << <<<
等, 位运算符 、算术运算符
隐式转化最难的情况
== !== >= <= < >
如下例子, 也是面试的高频题目,背下来之余,还是要知道得到这样结果的过程.
console.log([] == ![]) // true
个人觉得隐式转化最复杂的就是这个例子了.再复杂大不了加上优先级. 回到这个例子中, 看似比较的是两个数组,或者说两个特殊对象.其实不全是.来解析这个例子:
- 看到 等号 这个比较运算法就应该明白 等号 两边都要转化成Number类型
- 从左到右的话,Number([]), [] 是引用类型,无法直接拿到原始值
- valueOf拿不到值,就走
Array.prototype.toString.call([])
.从上面可以知道, 它返回的是去掉[],即字符串''. - 此时左边为Number(''). 所以左边返回的自然是0.
此时这个题目为0 == ![]
. 接下来右边的转化,这就简单了.
- 在Boolean()一节当中,就可以知道,除了falsey之外,其他都是ture.而此时在!的加持下,[]会进行Boolean()
- 此时右边为true. !true就为false
- Number(false)的结果为0
由上而得出 0 == 0 的结果为true.运用上面的知识点可以很好的解析问题,下面的这个例子就更加简单了.
console.log({} == {}) // false console.log({} != {})
如果按照一样的分析方式来解释的话:
- 两边都Number()包裹住.
- toString()之后都是[obejct Object]
Number('[obejct Object]')
为NaN- 所以最后转化为
console.log(NaN == NaN)
的比较
NaN和任何一个值比较都不想等
题目不难,但是综合的东西还是有一点的. 这两题解决了,隐式转化的问题也就到头了
三、特殊情况
最容易记住的就是字符串运算符.
console.log(1 + '2' + '2') // '122' console.log(1 + + '2' + '2') // '32' console.log('A' - 'B' + '2') // 'NaN2' console.log('A' - 'B' + 2) // NaN
js 字符串和任何数据类型想加都转化为字符串么?可以这么说,处理symbol类型直接报错之外.
console.log(typeof (+ '2')) // number
还有下面undfined和null的特殊情况
console.log(undefined == null) // true console.log(NaN == NaN) // fasle
- NaN的语意是not a number,很明显了,指的就不是一个数字
- NaN在typeof中是number类型,但是它和任何数都不想.
四、工作不要使用
2022年了,我们只需要了解==的运行机制就够了. 都这个年份了,不需要在重申工作中使用==还是===的问题了吧
总结
到此这篇关于js隐式转化的文章就介绍到这了,更多相关js的隐式转化内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!