[老生长谈]js 加法的规则总结

2018-08-18 16:19:25 +08:00
 rabbbit

自己总结了下 js 加法的密之转换规则,如果有错误还请多多指教

其实基本的转换规则只有 3 条
1 如果有对象,则先调用内部函数 ToPrimitive(先调用 valueOf,如果未返回基本数据类型再调用 toString)将其转换为基本数据类型(Undefined Null Boolean Number String)
2 如果有一个是字符串,将另一个转换成字符串(调用内部函数 ToString),返回两个字符串相加的结果
3 如果有一个是数值,将另一个转换成数值(调用内部函数 ToNumber),返回两个数字相加的结果

注 规则 2 的优先级高于规则 3.同时有字符串和数值时,把数值转成字符串

例子

'a' 是字符串,因此将另一个元素转换成字符串(通过 ToString)

'a' + 1 // a1
'a' + true // "atrue"
'a' + null // "anull"
'a' + undefined // "aundefined"

1 是数字,因此将另一个元素转为数字(通过 ToNumber)

1 + true // 2
1 + undefined // NaN
1 + null // 1

[]是对象,则通过 ToPrimitive 转换为字符串后相加

({}).valueOf() // []
// 返回来的是个对象,继续调用 toString
[].toString() // ""

[] + 1// "1"
[] + 'a' // "a"

{}是对象,则通过 ToPrimitive 转换为字符串后相加

({}).valueOf() // {}
// 返回来的是个对象,继续调用 toString
({}).toString() // "[object Object]"

1 + {} // "1[object Object]"
'a' + {} // "a[object Object]"

与对象相加一定返回字符串吗?不一定

var n = new Number(1)
typeof n // "object" n 是个对象,而非数值
n.valueOf() // 1 返回的是 Number,就不再调用 toString 了
n +  1 // 2 
// 因此 new Number(1) + 1 返回的是数值而不是字符串

如果修改 n 的 valueOf 方法,让其返回对象,则 n + 1 的结果是字符串而非数值

n.valueOf = function(){return []}
n + 1 // "11"

{}的异常

{} + 1 // 1
{} + 'a' // NaN
{} + '1' // 1
{} + [] // 0

在 firefox 和 ie 中(暂时不管 chrome),浏览器会把{}当作代码块.实际上执行的是一元加操作(在内部调用 ToNumbe),相当于

+1 // 1
+'a' // NaN
+'1' // 1
+[] // 0

给{}外面加个括号,结果就又不一样了.浏览器会正常解析为加法

({}) + 1 // "[object Object]1"
({})+ 'a' //"[object Object]a"
({})+ '1' // "[object Object]1"
({})+ [] // "[object Object]"

或者变成赋值语句也行

var a = {} + 1
console.log(a) // "[object Object]1"
a = ({})+ 'a' //"[object Object]a"

奇怪的 Chrome

chrome 是最让人摸不着头脑的
如果两个元素都是{}, 则将第一个元素当作对象处理(而非代码块)

{} + {} // "[object Object][object Object]"
{} + {a:1} // "[object Object][object Object]"

如果第二个元素不是{}(数组 字符串等),则将第一个元素当作代码块(实际上执行的是一元加操作)

{} + [] // 0
{} + 1 // 1
{} + 'a' // NaN
{} + '1' // 1
{} + null // 0

不过还是可以通过套括号来解决

({}) + [] // "[object Object]"
3141 次点击
所在节点    JavaScript
12 条回复
orwell1995
2018-08-18 16:22:51 +08:00
我想说,真的有必要纠结这些吗...
rabbbit
2018-08-18 16:23:28 +08:00
@orwell1995 面试爱考我也没办法
pelloz
2018-08-18 16:27:18 +08:00
面试考这些垃圾知识的公司不去也罢
isbase
2018-08-18 16:30:15 +08:00
整天研究这种糟糠,没有任何实际意义,写出这种代码的人直接滚蛋
azh7138m
2018-08-18 16:42:14 +08:00
https://tc39.github.io/ecma262/#sec-addition-operator-plus-runtime-semantics-evaluation

是说的这个顺序吧,我也好奇是哪家公司会问这个
msputup
2018-08-18 17:54:26 +08:00
说到面试,前几天去面试,问我
var obj1={'arr':1}

var obj2=obj1

obj2.arr=2

obj1.arr 是多少,我说当然是 2 啊,引用类型。

然后又说,那我定义一个 function,传入 obj2,然后修改 arr=2 呢,我说还是 2 啊

他说不对,这个传入的是值,obj1.arr 还是 1。

后来搞得我都怀疑自己了,回家 console.log 一下,发现自己没说错啊,日了
newghost
2018-08-18 19:34:47 +08:00
@msputup

你应该理解错了,

var obj1={'arr':1}
var obj2=obj1
var add = function(arr) { arr = 2 }
add(obj2.arr)
console.log(obj1)
> {'arr':1}
8qwe24657913
2018-08-18 20:02:41 +08:00
几个修正:
1. 规则第一条,优先级 wtf[Symbol.toPrimitive]('default') > wtf.valueOf() > wtf.toString(),基本数据类型还包括 symbol 和 bignum (思考: new Date() + 1 与 new Date().valueOf() + 1,Date 对象的表现在 ES5 中是强行写在规范里的,ES6 添加了 Symbol.toPrimitive 使它变得 "正常" 了一点)
2. 规则第三条,前提 "如果有一个是数值" 应改为 "否则" (思考: true + null)
3. Chrome 的 Console 做了特殊处理,以 "{" 开头以 "}" 结尾会被尝试加上小括号 (思考: {a:1} 和 {} + function(){} 以及 {} * class{})
msputup
2018-08-18 20:57:58 +08:00
@newghost 那看来是你说的这题,但是我反复确认下,他跟我说的传入的是对象,尴尬了
lihongjie0209
2018-08-18 21:15:45 +08:00
js 这种东西, 生态不行, 面试题也不行
skinny
2018-08-19 15:28:38 +08:00
我非常好奇为什么要问这些奇葩问题,如果没有编程约定,为什么不检查类型,或者转成字符串再拼接,一定要一股脑把不相同的东西用加号处理吗?
pinews
2019-05-04 09:10:45 +08:00
@skinny
@isbase
@pelloz
@orwell1995 道理是这个道理,但谁能避免没有人手贱写出这样的东西,
我们常常调侃一年前写的代码,突然发现自己看不懂了,专家说阅读代码比写代码更难,所以我们一边要求写代码尽可能的易读,但另一方面,如果真的遇到了,你不能撒手不管吧。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/480998

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX