写前感
果然做事要趁早,拖了太久就不想做了,比如说写隐式转换(二)这篇文章。之前想要写的思路时隔几天就逐渐忘记了。
但是做事正所谓要有始有终,如果现在不坚持的话,就和没有做没什么区别了。
这篇写什么
- ==, +, -等运算符涉及的隐式转换
- valueOf操作
- 原始值是什么
- toPrimitive操作
Go!
首先我们今天从一个规范开始。
规范就好像网络小说中的本源法则一样,是构成一切生物的规则。 而js规范就好像我们的本源法则,你也不需要知道为什么会这样,反正他就是这么规定的 ,并且一切生物都是按照这个规则在运转。 所以说规范虽然晦涩难懂,但是就好像本源大道一样,一通百通(虽然我也没有很懂哈哈哈
第一关:加号运算符
规范规定:
AdditiveExpression + MultiplicativeExpression这个运算会这样执行,用代码的方式可能会更容易理解
AdditiveExpression + MultiplicativeExpression
// 第一步
const lref = AdditiveExpression
// 第二步
const lval = GetValue(lref)
// 第三步
const rref = MultiplicativeExpression
// 第四步
const rval = GetValue(rref)
// 第五步
const lprim = ToPrimitive(lval)
// 第六步
const rprim = ToPrimitive(rval)
// 第七步:如果此时 Type(lprim)或Type(rprim) 类型为String,返回ToString(lprim)与ToString(rprim)的拼接
// 第八步:返回ToNumber(lprim)和ToNumber(rprim)的结果
其中GetValue和ToPrimitive将在后续解答,目前理解为一个函数即可。
当前总会有那么写个特殊情况,针对这些特殊情况,我们看看了解即可
- 有运算数为NaN时,结果为NaN
- -Infinity加-Infinity,结果为-Infinity(此处补充下:两个正的加起来是Infinity,因为本来就是无穷大了=
- Infinity加-Infinity,结果为NaN(你以为是0?不你错了
- +0加+0,结果为+0
- -0加+0,结果为+0
- -0加-0,结果为-0
小贴士:此处的Infinity和-Infinity分别是代表正无穷和负无穷
第二关:减号运算符
说了加号运算符,就要说说减号运算符了。不过毕竟是减法,在规则上都是减的,让我们来看看减号运算符的规范吧。
同样的,由于规范的文字描述实在是有点绕,所以这里还是以代码的形式相对直观的展示出来
规范规定:
AdditiveExpression - MultiplicativeExpression
// 还是和加法同样的一到四步,不多bb
const lref = AdditiveExpression
const lval = GetValue(lref)
const rref = MultiplicativeExpression
const rval = GetValue(rref)
// 注意第五和第六步开始出现区别了(加法这里是ToPrimitive哦
const lnum = ToNumber(lval)
const rnum = ToNumber(rval)
// 第七步:将减法运算符作用于ToNumber(lprim)和ToNumber(rprim)
减法这里就明显简单了一些GetValue虽然现在还是不知道,但是ToNumber你还不知道吗?
当然对于可恶的特殊值,也会有一些特殊规则,这里列举一下,可以选择性跳过先哈哈
- 有运算数为NaN时,结果为NaN
- nfinity减Infinity,结果为NaN(并不是0
- -Infinity减-Infinity,结果为NaN(我还以为会是0
- Infinity减-Infinity,结果为Infinity(这个类比Infinity加Infinity
- -Infinity减Infinity,结果为-Infinity(毕竟已经负无穷
- +0减+0,结果为+0
- -0减-0,结果为-0
- +0减-0,结果为+0
- 有不是数字的,结果为NaN
一定会有人问, 为什么不提前先解答一下GetValue和toPrimitive呢? ️ 原因其实很简单 如果不抱着点疑问和好奇,开局就给你来这么多晦涩难懂的东西你不就关了这篇文章吗?!而且你会直接看的进去吗!
第三关:ToPrimitive(划重点)
原始值
在介绍ToPrimitive之前首先要介绍原始值的概念,众所周知,在Javascript中一共有两种类型的值
(此处暂不提Symbol
一种是原始值(primitives):
- undefined
- null
- boolean
- number
- string
另一种就是对象值(object):
- object
- array
- function
- date
- ...
而ToPrimitive其实就是把对象变成原始值的操作啦~
ToPrimitive,这里其实就是一个js引擎内部的一个函数,这个函数会有两个参数,大概长这样
ToPrimitive(obj, preferredType) // 后面会模拟实现一下
// 其中obj是传过来的值,也就是被转换的对象,必填!
// preferredType则是被希望转换成的类型,默认是空,可以是Number | String
正常的小朋友被此函数调用默认第二个参数是Number,而不正常的小朋友(Date就是你)被此函数调用默认第二个参数则是String。
下面我们看看第二个参数为Number和String时的执行有什么不同
首先是ToPrimitive(obj, Number)
- obj是原始值,直接返回
- 调用obj.valueOf(),如果执行结果是原始值,返回
- 调用obj.toString(),如果执行结果是原始值,返回
- 实在拿你没办法了,抛异常了!
然后是ToPrimitive(obj, String) 其实和上面一模一样,只是顺序变成了1 3 2 4
由此可见,其实只是先valueOf和先toString的区别了
第四关:valueOf与toString
这时陈明明同学要求:这个toString和valueOf你说说呗。
,没问题
首先,toString()方法可以理解为把你见到的所有东西全部字符串化,我们来试试看
var t1 = 1
t1.toString() // '1'
var t2 = true
t1.toString() // 'true'
var t3 = NaN
t3.toString() // 'NaN'
var t4 = undefined
t4.toString() // Error,本身就undefined,啥也没有要什么自行车
var t5 = []
t5.toString() // '' 这个可以注意下
var t6 = function(){ ... }
t6.toString() // 'function(){ ... }'
而valueOf呢,就是返回对象的原始值
原始值就是我们上面所说的undefined,null,number,string,boolean
但是这里有三个需要注意一下
var o1 = { name: 'o1' }
o1.valueOf() // {name: 'o1'}
var o2 = [1]
o2.valueOf() // [1]
var o3 = new Date()
o3.valueOf() // 1562982586913
下面我们来试着写一下ToPrimitive()
function ToPrimitive(obj, preferredType) {
const utils = {
typeOf: function (obj) {
return Object.prototype.toString.call(obj).slice(8, -1)
},
isPrimitive: function (obj) {
const type = this.typeOf(obj)
const primitiveArr = ['Undefined', 'Null', 'Number', 'String', 'Boolean']
return primitiveArr.indexOf(type) !== -1
}
}
// 先判断是不是原始值
if (utils.isPrimitive(obj)) {
return obj.valueOf()
}
// Date和preferredType === 'String'是先toString再视返回结果执行valueOf()
if (utils.typeOf(obj) === 'Date' | preferredType === 'String') {
const res = obj.toString()
return utils.isPrimitive(res) ? res : res.valueOf()
}
// 是原始值valueOf(), 然后视返回结果执行toString()
const res = obj.valueOf()
return utils.isPrimitive(res) ? res : res.toString()
}
ToPrimitive({}) // [object Object]
第五关:思考题目
++[[]][+[]]+[+[]] == 10?
// 这是我的思路:
// 1. 由上节一元运算符+我们可知+[]结果为0,所以化简为:
++[[]][0]+[0]
// 2. [[]][0]可以化简为[],可得:
++[]+[0]
// 3. ++[]为ToPrimitive([])+1,所以可得:
1+[0]
// 4. +[0]相当于[0].valueOf().toString() 最终为'0'(不要以为会变成‘[0]’,我就错在这里了
// 5. 所以结果为 1+‘0’ 返回 ‘10’
‘10’ == 10 // true
++[[]][+[]]+[+[]] == 10 // 判断为true
延伸思考
后续我思考过:ToPrimitive([[]])最后实验发现结果为“” 在测试过[1, [2], [3,[4,{}]]]...之后我发现,最终ToPrimitive其实可以理解为无视数组层级,最后都会变成以字符串拼接的平铺方式展示
第六关:== 怎么搞?
先看规范,没错还是规范!
规范里面写的很清楚,但是把我看吐了。
这里总结概括了一下:
运算x == y时,区分两种情况:
- Type(x) == Type(y)
- Type(x) != Type(y)
Type(x) == Type(y)时规则总结下来就是:
- NaN返回false
- Object返回false
- 其他均为true
这里的Object包括Array,Function,Date
Type(x) != Type(y)时规则可能比较多:
- Type(x)为String,长度和字符位置都相同返回true,其余false
- x,y为引用的为同一个对象时返回true
- Undefined和Null对比是true
- x,y分别为Number/String时,根据x的类型来判断
- Object与Number/String比较时,进行ToPrimitive操作后比较
- 其他全是false
没办法 ♂️,这东西我们记住就行了,这就是规范
今日总结
知识点:
- 加号运算符的运算:先转化原始值,字符串优先,其次Number运算。
- 减号运算符的运算:直接Number运算,不服的都去NaN。
- 原始值:Null,Undefined,Number,String,Boolean。
- ToPrimitive(obj, preferredType):valueOf()和toString(),默认为Number先valueOf(),值为String先toString(),注意Date这个小bitch的特殊情况。
- valueOf: 不是原始值就该返回啥返回啥,但是还是要注意Date这个小bitch的特殊情况。
- ==运算符: undefined和null是好兄弟可以等,对象的话每个对象都不一样,除非是同一个引用
作者想说的
这篇可能是我目前内容量最大的一篇Blog了,可能内容不如之前那么通俗易懂,但是很多都是规范性质的(再加上我又比较菜哈哈哈 。
我尽可能把它转化成一些易懂的话分享给大家,参考了篇大神的文章,他写的真的很细致,在此感谢作者。但是他的内容量真的很大很大,所以我觉得可能拆分一下精简一下吸收可能更有效吧。
也是我自己学习的一个历程。
我是想写在文章里的,可是知乎一直删,不知道为什么原文链接疯狂被删,很难受