[译]JavaScript中,{}+{}等于多少?

白昼怎懂夜的黑 提交于 2020-01-12 17:07:04

今天面试不小心掉进坑了,大公司特别喜欢考javascript,而且专门挑很tricky的case。

javascipt的==简直就是黑魔法,以前偷懒总是用,感觉也没有问题,可是准备面试就需要有寻根问底的精神。

原题问[]==false; ![]==false console输出什么。结果是都是true 

当空数组作为判断条件时,相当于true。当空数组与布尔值直接比较时,相当于false。

[] == ![];

// true
 
[ ] == true
// false

Since the left and right sides of the equality are two different types, JavaScript can't compare them directly. Hence, under the hood, JavaScript will convert them to compare. First, the right side of the equality will be cooereced to a number and number of true would be 1.

After that, JavaScript implementation will try to convert [] by usingtoPrimitive (of JavaScript implementation). Since [].valueOf is not primitive, it will use toString and will get ""

Now you are comparing "" == 1 and still, the left and right are not the same type. Hence, the left side will be converted again to a number and empty string will be 0.

Finally, they are of same type. You are comparing 0 === 1 which will be false.

一般做逻辑判断应该是转换为bool类型,javascript最终却是转换为数字来比较。

 

1.一个数字与一个字符串,字符串转换成数字之后,进行比较。
2. true转换为1,false转换为0进行比较。// 1==true返回true;2==true返回false;
3. 数组会被转换为原始类型之后进行比较(先valueof,不行的话再toString)。var a = [1,2], a==true这里的a会被转换为"1,2"又因为true被转成了数字,所以这里的a最终被转成Number(a),也就是NaN。
结合上面3点得出a==true最终变成 NaN==1 ,所以返回false。[1]==true;//true;[2]==true;//false!!

 

参见ecma262:http://www.ecma-international.org/ecma-262/6.0/index.html#sec-abstract-equality-comparison

7.2.12Abstract Equality Comparison

The comparison x == y, where x and y are values, produces true or false. Such a comparison is performed as follows:

    1. ReturnIfAbrupt(x).
    2. ReturnIfAbrupt(y).
    3. If Type(x) is the same as Type(y), then
      1. Return the result of performing Strict Equality Comparison x === y.
    4. If x is null and y is undefined, return true.
    5. If x is undefined and y is null, return true.
    6. If Type(x) is Number and Type(y) is String,
      return the result of the comparison x == ToNumber(y).
    7. If Type(x) is String and Type(y) is Number,
      return the result of the comparison ToNumber(x) == y.
    8. If Type(x) is Boolean, return the result of the comparison ToNumber(x) == y.
    9. If Type(y) is Boolean, return the result of the comparison x == ToNumber(y).
    10. If Type(x) is either String, Number, or Symbol and Type(y) is Object, then
      return the result of the comparison x == ToPrimitive(y).
    11. If Type(x) is Object and Type(y) is either String, Number, or Symbol, then
      return the result of the comparison ToPrimitive(x) == y.
    12. Return false.

下面是转载的相关内容 

 

 

在js中,想要将对象转换成原始值,必然会调用toPrimitive()内部函数,那么它是如何工作的呢?

    该函数形式如下:

toPrimitive(input,preferedType?)
    input是输入的值,preferedType是期望转换的类型,他可以是字符串,也可以是数字。

    如果转换的类型是number,会执行以下步骤:

     1. 如果input是原始值,直接返回这个值;

     2. 否则,如果input是对象,调用input.valueOf(),如果结果是原始值,返回结果;

     3. 否则,调用input.toString()。如果结果是原始值,返回结果;

     4. 否则,抛出错误。

     如果转换的类型是String,2和3会交换执行,即先执行toString()方法。

    你也可以省略preferedType,此时,日期会被认为是字符串,而其他的值会被当做Number。

    综上所述,会有以下计算结果:

>[]+[]
>""
加号操作符会将preferedType看成Number,调用ES内部的toPrimitive(input,Number)方法,得到空字符串

>[]+{}
>"[object Object]"
 最终会调用双方的toString()方法,再做字符串加法

>{}+[]
>0
但是空对象加空数组就不一样了,加号运算符的定义是这样的:如果其中一个是字符串,另一个也会被转换为字符串,否则两个运算数都被转换为数字。而同时,javascript有这样的特性,如果{}既可以被认为是代码块,又可以被认为是对象字面量,那么js会把他当做代码块来看待。

这就很好解释了,{}被当做了代码块,只有+[],根据加法的定义,被转换为0,就得到了结果。

在操作符中,==,排序运算符,加减乘除,在对非原始值进行操作时,都会调用内部的toPrimitive()方法
---------------------
作者:苏雪冷音
来源:CSDN
原文:https://blog.csdn.net/suxuelengyin/article/details/82759437

 

[译]JavaScript中,{}+{}等于多少?

原文:http://www.2ality.com/2012/01/object-plus-object.html


最近,Gary Bernhardt在一个简短的演讲视频“Wat”中指出了一个有趣的JavaScript怪癖:在把对象和数组混合相加时,会得到一些你意想不到的结果.本篇文章会依次讲解这些计算结果是如何得出的.

JavaScript-wat

在JavaScript中,加法的规则其实很简单,只有两种情况:你只能把数字和数字相加,或者字符串和字符串相加,所有其他类型的值都会被自动转换成这两种类型的值. 为了能够弄明白这种隐式转换是如何进行的,我们首先需要搞懂一些基础知识.注意:在下面的文章中提到某一章节的时候(比如§9.1),指的都是ECMA-262语言规范(ECMAScript 5.1)中的章节.

让我们快速的复习一下.在JavaScript中,一共有两种类型的值:原始值(primitives)和对象值(objects).原始值有:undefined, null, 布尔值(booleans), 数字(numbers),还有字符串(strings).其他的所有值都是对象类型的值,包括数组(arrays)和函数(functions).

1.类型转换

加法运算符会触发三种类型转换:将值转换为原始值,转换为数字,转换为字符串,这刚好对应了JavaScript引擎内部的三种抽象操作:ToPrimitive(),ToNumber(),ToString()

1.1 通过ToPrimitive()将值转换为原始值

JavaScript引擎内部的抽象操作ToPrimitive()有着这样的签名:

    ToPrimitive(input, PreferredType?)

可选参数PreferredType可以是Number或者String,它只代表了一个转换的偏好,转换结果不一定必须是这个参数所指的类型,但转换结果一定是一个原始值.如果PreferredType被标志为Number,则会进行下面的操作来转换输入的值 (§9.1):

  1. 如果输入的值已经是个原始值,则直接返回它.
  2. 否则,如果输入的值是一个对象.则调用该对象的valueOf()方法.如果valueOf()方法的返回值是一个原始值,则返回这个原始值.
  3. 否则,调用这个对象的toString()方法.如果toString()方法的返回值是一个原始值,则返回这个原始值.
  4. 否则,抛出TypeError异常.

如果PreferredType被标志为String,则转换操作的第二步和第三步的顺序会调换.如果没有PreferredType这个参数,则PreferredType的值会按照这样的规则来自动设置:Date类型的对象会被设置为String,其它类型的值会被设置为Number.

1.2 通过ToNumber()将值转换为数字

下面的表格解释了ToNumber()是如何将原始值转换成数字的 (§9.3).

参数 结果
undefined NaN
null +0
布尔值 true被转换为1,false转换为+0
数字 无需转换
字符串 由字符串解析为数字.例如,"324"被转换为324

如果输入的值是一个对象,则会首先会调用ToPrimitive(obj, Number)将该对象转换为原始值,然后在调用ToNumber()将这个原始值转换为数字.

1.3 通过ToString()将值转换为字符串

下面的表格解释了ToString()是如何将原始值转换成字符串的(§9.8).

参数 结果
undefined "undefined"
null "null"
布尔值 "true"  或者 "false"
数字 数字作为字符串,比如. "1.765"
字符串 无需转换

如果输入的值是一个对象,则会首先会调用ToPrimitive(obj, String)将该对象转换为原始值,然后再调用ToString()将这个原始值转换为字符串.

1.4 实践一下

下面的对象可以让你看到引擎内部的转换过程.

var obj = {
    valueOf: function () {
        console.log("valueOf");
        return {}; // 没有返回原始值
    },
    toString: function () {
        console.log("toString");
        return {}; // 没有返回原始值
    }
}

Number作为一个函数被调用(而不是作为构造函数调用)时,会在引擎内部调用ToNumber()操作:

> Number(obj)
valueOf
toString
TypeError: Cannot convert object to primitive value 

2.加法

有下面这样的一个加法操作.

    value1 + value2

在计算这个表达式时,内部的操作步骤是这样的 (§11.6.1):

  1. 将两个操作数转换为原始值 (下面是数学表示法,不是JavaScript代码):
        prim1 := ToPrimitive(value1)    prim2 := ToPrimitive(value2)
    PreferredType被省略,因此Date类型的值采用String,其他类型的值采用Number.
  2. 如果prim1或者prim2中的任意一个为字符串,则将另外一个也转换成字符串,然后返回两个字符串连接操作后的结果.
  3. 否则,将prim1和prim2都转换为数字类型,返回他们的和.

2.1 预料到的结果

两个空数组相加时,结果是我们所预料的:

> [] + []
''

[]会被转换成一个原始值,首先尝试valueOf()方法,返回数组本身(this):

> var arr = [];
> arr.valueOf() === arr
true

这样的结果不是原始值,所以再调用toString()方法,返回一个空字符串(是一个原始值).因此,[] + []的结果实际上是两个空字符串的连接.

将一个空数组和一个空对象相加,结果也符合我们的预期:

> [] + {}
'[object Object]'

类似的,空对象转换成字符串是这样的.

> String({})
'[object Object]'

所以最终的结果是 """[object Object]" 两个字符串的连接.

下面是更多的对象转换为原始值的例子,你能搞懂吗:

> 5 + new Number(7)
12
> 6 + { valueOf: function () { return 2 } }
8
> "abc" + { toString: function () { return "def" } }
'abcdef'

2.1 意想不到的结果

如果加号前面的第一个操作数是个空对象字面量,则结果会出乎我们的意料(下面的代码在Firefox控制台中运行):

> {} + {}
NaN

这是怎么一回事?原因就是JavaScript引擎将第一个{}解释成了一个空的代码块并忽略了它.NaN其实是后面的表达式+{}计算的结果 (加号以及后面的{}).这里的加号并不是代表加法的二元运算符,而是一个一元运算符,作用是将它后面的操作数转换成数字,和Number()函数完全一样.例如:

> +"3.65"
3.65

转换的步骤是这样的:

+{}
Number({})
Number({}.toString())  // 因为{}.valueOf()不是原始值
Number("[object Object]")
NaN

为什么第一个{}会被解析成代码块呢?原因是,整个输入被解析成了一个语句,如果一个语句是以左大括号开始的,则这对大括号会被解析成一个代码块.所以,你也可以通过强制把输入解析成一个表达式来修复这样的计算结果:

> ({} + {})
'[object Object][object Object]'

另外,一个函数或方法的参数也会被解析成一个表达式:

> console.log({} + {})
[object Object][object Object]

经过前面的这一番讲解,对于下面这样的计算结果,你也应该不会感到吃惊了:

> {} + []
0

在解释一次,上面的输入被解析成了一个代码块后跟一个表达式+[].转换的步骤是这样的:

+[]
Number([])
Number([].toString())  // 因为[].valueOf()不是原始值
Number("")
0

有趣的是,Node.js的REPL在解析类似的输入时,与Firefox和Chrome(和Node.js一样使用V8引擎)的解析结果不同.下面的输入会被解析成一个表达式,结果更符合我们的预料:

> {} + {}
'[object Object][object Object]'
> {} + []
'[object Object]'

下面是SpiderMonkey 和 nodejs 中的结果对比.

3.其他

在大多数情况下,想要弄明白JavaScript中的+号是如何工作的并不难:你只能将数字和数字相加或者字符串和字符串相加.对象值会被转换成原始值后再进行计算.如果你想连接多个数组,需要使用数组的concat方法:

> [1, 2].concat([3, 4])
[ 1, 2, 3, 4 ]

JavaScript中没有内置的方法来“连接" (合并)多个对象.你可以使用一个JavaScript库,比如Underscore:

> var o1 = {eeny:1, meeny:2};
> var o2 = {miny:3, moe: 4};
> _.extend(o1, o2)
{ eeny: 1,
  meeny: 2,
  miny: 3,
  moe: 4 }

注意:和Array.prototype.concat()方法不同,extend()方法会修改它的第一个参数,而不是返回合并后的对象:

> o1
{ eeny: 1,
  meeny: 2,
  miny: 3,
  moe: 4 }
> o2
{ miny: 3, moe: 4 }

如果你想了解更多有趣的关于运算符的知识,你可以阅读一下“Fake operator overloading in JavaScript”(已墙).

4.参考

  1. JavaScript values: not everything is an object
 
posted @ 2012-09-15 14:39 阅读(12246) 评论(6)

原文:http://www.2ality.com/2012/01/object-plus-object.html


最近,Gary Bernhardt在一个简短的演讲视频“Wat”中指出了一个有趣的JavaScript怪癖:在把对象和数组混合相加时,会得到一些你意想不到的结果.本篇文章会依次讲解这些计算结果是如何得出的.

JavaScript-wat

在JavaScript中,加法的规则其实很简单,只有两种情况:你只能把数字和数字相加,或者字符串和字符串相加,所有其他类型的值都会被自动转换成这两种类型的值. 为了能够弄明白这种隐式转换是如何进行的,我们首先需要搞懂一些基础知识.注意:在下面的文章中提到某一章节的时候(比如§9.1),指的都是ECMA-262语言规范(ECMAScript 5.1)中的章节.

让我们快速的复习一下.在JavaScript中,一共有两种类型的值:原始值(primitives)和对象值(objects).原始值有:undefined, null, 布尔值(booleans), 数字(numbers),还有字符串(strings).其他的所有值都是对象类型的值,包括数组(arrays)和函数(functions).

1.类型转换

加法运算符会触发三种类型转换:将值转换为原始值,转换为数字,转换为字符串,这刚好对应了JavaScript引擎内部的三种抽象操作:ToPrimitive(),ToNumber(),ToString()

1.1 通过ToPrimitive()将值转换为原始值

JavaScript引擎内部的抽象操作ToPrimitive()有着这样的签名:

    ToPrimitive(input, PreferredType?)

可选参数PreferredType可以是Number或者String,它只代表了一个转换的偏好,转换结果不一定必须是这个参数所指的类型,但转换结果一定是一个原始值.如果PreferredType被标志为Number,则会进行下面的操作来转换输入的值 (§9.1):

  1. 如果输入的值已经是个原始值,则直接返回它.
  2. 否则,如果输入的值是一个对象.则调用该对象的valueOf()方法.如果valueOf()方法的返回值是一个原始值,则返回这个原始值.
  3. 否则,调用这个对象的toString()方法.如果toString()方法的返回值是一个原始值,则返回这个原始值.
  4. 否则,抛出TypeError异常.

如果PreferredType被标志为String,则转换操作的第二步和第三步的顺序会调换.如果没有PreferredType这个参数,则PreferredType的值会按照这样的规则来自动设置:Date类型的对象会被设置为String,其它类型的值会被设置为Number.

1.2 通过ToNumber()将值转换为数字

下面的表格解释了ToNumber()是如何将原始值转换成数字的 (§9.3).

参数 结果
undefined NaN
null +0
布尔值 true被转换为1,false转换为+0
数字 无需转换
字符串 由字符串解析为数字.例如,"324"被转换为324

如果输入的值是一个对象,则会首先会调用ToPrimitive(obj, Number)将该对象转换为原始值,然后在调用ToNumber()将这个原始值转换为数字.

1.3 通过ToString()将值转换为字符串

下面的表格解释了ToString()是如何将原始值转换成字符串的(§9.8).

参数 结果
undefined "undefined"
null "null"
布尔值 "true"  或者 "false"
数字 数字作为字符串,比如. "1.765"
字符串 无需转换

如果输入的值是一个对象,则会首先会调用ToPrimitive(obj, String)将该对象转换为原始值,然后再调用ToString()将这个原始值转换为字符串.

1.4 实践一下

下面的对象可以让你看到引擎内部的转换过程.

var obj = {
    valueOf: function () {
        console.log("valueOf");
        return {}; // 没有返回原始值
    },
    toString: function () {
        console.log("toString");
        return {}; // 没有返回原始值
    }
}

Number作为一个函数被调用(而不是作为构造函数调用)时,会在引擎内部调用ToNumber()操作:

> Number(obj)
valueOf
toString
TypeError: Cannot convert object to primitive value 

2.加法

有下面这样的一个加法操作.

    value1 + value2

在计算这个表达式时,内部的操作步骤是这样的 (§11.6.1):

  1. 将两个操作数转换为原始值 (下面是数学表示法,不是JavaScript代码):
        prim1 := ToPrimitive(value1)    prim2 := ToPrimitive(value2)
    PreferredType被省略,因此Date类型的值采用String,其他类型的值采用Number.
  2. 如果prim1或者prim2中的任意一个为字符串,则将另外一个也转换成字符串,然后返回两个字符串连接操作后的结果.
  3. 否则,将prim1和prim2都转换为数字类型,返回他们的和.

2.1 预料到的结果

两个空数组相加时,结果是我们所预料的:

> [] + []
''

[]会被转换成一个原始值,首先尝试valueOf()方法,返回数组本身(this):

> var arr = [];
> arr.valueOf() === arr
true

这样的结果不是原始值,所以再调用toString()方法,返回一个空字符串(是一个原始值).因此,[] + []的结果实际上是两个空字符串的连接.

将一个空数组和一个空对象相加,结果也符合我们的预期:

> [] + {}
'[object Object]'

类似的,空对象转换成字符串是这样的.

> String({})
'[object Object]'

所以最终的结果是 """[object Object]" 两个字符串的连接.

下面是更多的对象转换为原始值的例子,你能搞懂吗:

> 5 + new Number(7)
12
> 6 + { valueOf: function () { return 2 } }
8
> "abc" + { toString: function () { return "def" } }
'abcdef'

2.1 意想不到的结果

如果加号前面的第一个操作数是个空对象字面量,则结果会出乎我们的意料(下面的代码在Firefox控制台中运行):

> {} + {}
NaN

这是怎么一回事?原因就是JavaScript引擎将第一个{}解释成了一个空的代码块并忽略了它.NaN其实是后面的表达式+{}计算的结果 (加号以及后面的{}).这里的加号并不是代表加法的二元运算符,而是一个一元运算符,作用是将它后面的操作数转换成数字,和Number()函数完全一样.例如:

> +"3.65"
3.65

转换的步骤是这样的:

+{}
Number({})
Number({}.toString())  // 因为{}.valueOf()不是原始值
Number("[object Object]")
NaN

为什么第一个{}会被解析成代码块呢?原因是,整个输入被解析成了一个语句,如果一个语句是以左大括号开始的,则这对大括号会被解析成一个代码块.所以,你也可以通过强制把输入解析成一个表达式来修复这样的计算结果:

> ({} + {})
'[object Object][object Object]'

另外,一个函数或方法的参数也会被解析成一个表达式:

> console.log({} + {})
[object Object][object Object]

经过前面的这一番讲解,对于下面这样的计算结果,你也应该不会感到吃惊了:

> {} + []
0

在解释一次,上面的输入被解析成了一个代码块后跟一个表达式+[].转换的步骤是这样的:

+[]
Number([])
Number([].toString())  // 因为[].valueOf()不是原始值
Number("")
0

有趣的是,Node.js的REPL在解析类似的输入时,与Firefox和Chrome(和Node.js一样使用V8引擎)的解析结果不同.下面的输入会被解析成一个表达式,结果更符合我们的预料:

> {} + {}
'[object Object][object Object]'
> {} + []
'[object Object]'

下面是SpiderMonkey 和 nodejs 中的结果对比.

3.其他

在大多数情况下,想要弄明白JavaScript中的+号是如何工作的并不难:你只能将数字和数字相加或者字符串和字符串相加.对象值会被转换成原始值后再进行计算.如果你想连接多个数组,需要使用数组的concat方法:

> [1, 2].concat([3, 4])
[ 1, 2, 3, 4 ]

JavaScript中没有内置的方法来“连接" (合并)多个对象.你可以使用一个JavaScript库,比如Underscore:

> var o1 = {eeny:1, meeny:2};
> var o2 = {miny:3, moe: 4};
> _.extend(o1, o2)
{ eeny: 1,
  meeny: 2,
  miny: 3,
  moe: 4 }

注意:和Array.prototype.concat()方法不同,extend()方法会修改它的第一个参数,而不是返回合并后的对象:

> o1
{ eeny: 1,
  meeny: 2,
  miny: 3,
  moe: 4 }
> o2
{ miny: 3, moe: 4 }

如果你想了解更多有趣的关于运算符的知识,你可以阅读一下“Fake operator overloading in JavaScript”(已墙).

4.参考

  1. JavaScript values: not everything is an object
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!