最近的一条tweet包含了下面的JavaScript代码片段。

谁能一步一步解释一下里面发生了什么?

> function dis() { return this }
undefined
> five = dis.call(5)
Number {[[PrimitiveValue]]: 5}
> five.wtf = 'potato'
"potato"
> five.wtf
"potato"
> five * 5
25
> five.wtf
"potato"
> five++
5
> five.wtf
undefined
> five.wtf = 'potato?'
"potato?"
> five.wtf
undefined
> five
6

特别是,我不清楚:

为什么dis.call(5)的结果是一个带有[[PrimitiveValue]]属性的数字,但是5 ++和5 * 5的结果似乎只是普通的数字5和25(不是数字) 为什么是五个。WTF属性在5++增量之后消失 为什么是五个。WTF属性在5++增量之后甚至不再是可设置的,尽管five。WTF =土豆?'赋值显然设置了值。


当前回答

声明函数dis。函数返回其上下文

function dis() { return this }
undefined

用context 5调用dis。原始值在严格模式(MDN)中作为上下文传递时被装箱。所以5现在是object(盒装数字)

five = dis.call(5)
Number {[[PrimitiveValue]]: 5}

在五个变量上声明wtf属性

five.wtf = 'potato'
"potato"

5 .wtf的值

five.wtf
"potato"

5被框5,所以它同时是数字和对象(5 * 5 = 25)。它不会改变5。

five * 5
25

5 .wtf的值

five.wtf
"potato"

在这里打开5。5现在只是一个原始数。它输出5,然后5加1。

five++
5

5现在是原数6,它没有任何属性。

five.wtf
undefined

基本类型不能有属性,你不能设置这个

five.wtf = 'potato?'
"potato?"

你不能读这个,因为没有设置

five.wtf
undefined

5是6,因为上面的增量

five
6

其他回答

原始值不能有属性。但是,当您试图访问原始值上的属性时,它会透明地转换为临时Number对象。

So:

> function dis() { return this }
undefined
// Like five.dis(), so dis return the temporaty Number object and 
// reference it in five
> five = dis.call(5)
Number {[[PrimitiveValue]]: 5}

// Write the wtf attribut on the Number object referenced by five
> five.wtf = 'potato'
"potato"
// Read the wtf attribut on the Number object referenced by five
> five.wtf
"potato"

// Return 5*5 but dont change the reference of five
> five * 5
25
// Read the same wtf attribut on the Number object referenced by five
> five.wtf
"potato"

// Change the five reference to a new primitive value (5+1). Five
// reference a primitive now.
> five++
5

// Read the wtf attribut on a new temporary Number object construct from
// the primitive referenced by five. So wtf does not exist.
> five.wtf
undefined

// Write the wtf attribut on a new temporary Number object construct from
// the primitive referenced by five. But this object not referenced by
// five. It will be lost.
> five.wtf = 'potato?'
"potato?"

// Read the wtf attribut on a new temporary Number object construct from
// the primitive referenced by five. So wtf does not exist.
> five.wtf
undefined
> five
6

首先,它看起来是通过nodejs控制台运行的。

1.

    function dis() { return this }

创建函数dis(),但由于它没有设置为var,因此没有返回值,因此输出为undefined,即使dis()已定义。顺便说一句,这个没有返回,因为函数没有执行。

2.

    five = dis.call(5)

这将返回javascript的Number对象,因为您刚刚将函数dis()的This值设置为原语5。

3.

   five.wtf = 'potato'

第一个返回“potato”,因为您刚刚将属性wtf(5)设置为“potato”。Javascript返回你所设置的变量的值,这样就可以很容易地将多个变量链接起来,并将它们设置为相同的值,如下所示:a = b = c = 2。

4.

    five * 5

这个返回25,因为你刚刚把原数5乘以了5。5的值由Number对象的值决定。

5.

    five.wtf

我之前跳过了这一行因为我要在这里重复。 它只是返回你在上面设置的属性wtf的值。

6.

    five++

正如@Callum所说,++将从对象number {[[PrimitiveValue]]: 5}}中的相同值将类型转换为数字。

现在因为5是一个数字,你不能再为它设置属性,直到你做这样的事情:

    five = dis.call(five)
    five.wtf = "potato?"

or

    five = { value: 6, wtf: "potato?" }

还要注意,第二种方法与使用第一种方法的行为不同,因为它定义的是一个泛型对象,而不是之前创建的Number对象。

我希望这能有所帮助,javascript喜欢假设事情,所以当从数字对象更改为原始数字时,它可能会令人困惑。 你可以使用typeof关键字来检查某个东西是什么类型 typeof五 初始化后,它返回'object',执行5++后,它返回'number'。

@ desze非常好地描述了Number对象和基本数之间的区别。

OP。在Stack Overflow上看到这个很有趣:)

在详细介绍这种行为之前,有几件事是很重要的:

Number value and Number object (a = 3 vs a = new Number(3)) are very different. One is a primitive, the other is an object. You cannot assign attributes to primitives, but you can to objects. Coercion between the two is implicit. For example: (new Number(3) === 3) // returns false (new Number(3) == 3) // returns true, as the '==' operator coerces (+new Number(3) === 3) // returns true, as the '+' operator coerces Every Expression has a return value. When the REPL reads and executes an expression, this is what it displays. The return values often don't mean what you think and imply things that just aren't true.

好了,开始吧。

承诺。

> function dis() { return this }
undefined
> five = dis.call(5)
[Number: 5]

定义一个函数dis并使用5调用它。这将以5作为上下文(This)执行函数。这里它是从一个Number值强制转换为一个Number对象。需要注意的是,如果我们处于严格模式,这种情况就不会发生。

> five.wtf = 'potato'
'potato'
> five.wtf
'potato'

现在我们设置属性5。wtf转换为'potato',并以5作为对象,它当然接受简单赋值。

> five * 5
25
> five.wtf
'potato'

以5为对象,我确保它仍然可以执行简单的算术运算。它可以。它的属性仍然存在吗?是的。

把。

> five++
5
> five.wtf
undefined

现在我们检查5++。后缀增量的技巧是,整个表达式将根据原始值求值,然后再增加该值。看起来5还是5,但实际上表达式的值是5,然后把5设为6。

不仅5被设置为6,而且它还被强制返回为Number值,所有属性都丢失了。由于原语不能保存属性,所以是5。WTF未定义。

> five.wtf = 'potato?'
'potato?'
> five.wtf
undefined

我再次尝试将属性wtf重新分配给5。返回值表明它是固定的,但实际上不是,因为five是一个Number值,而不是一个Number对象。表达式的计算结果是'potato?’,但当我们检查时,我们发现它没有分配。

信誉。

> five
6

从后缀的增量开始,5就变成了6。

有几个概念可以解释发生了什么

5是一个数字,一个原始值

Number {[[PrimitiveValue]]: 5}是Number的一个实例(我们称它为对象包装器)

每当你访问一个基本值的属性/方法时,JS引擎将创建一个适当类型的对象包装器(Number为5,String为'str', Boolean为true),并解析该对象包装器上的属性访问/方法调用。这就是执行true.toString()时所发生的情况。

当对对象执行操作时,它们被转换为基本值(通过使用toString或valueOf)以解析这些操作—例如在做

var obj = { a : 1 };
var string = 'mystr' + obj;
var number = 3 + obj;

string将保存mystr和obj.toString()的字符串连接,number将保存3和obj.valueOf()的加法。

现在把它们放在一起

five = dis.call(5)

dis.call(5)的行为就像(5).dis(),如果5实际上有方法dis。为了解析方法调用,创建对象包装器并在其上解析方法调用。此时,five指向围绕原语值5的对象包装器。

five.wtf = 'potato'

在对象上设置属性,这里没什么特别的。

five * 5

这实际上是five.valueOf() * 5从对象包装器获取原始值。5仍然指向初始对象。

five++

这实际上是5 = 5 . valueof() + 1。在这一行之前,第5行保存了围绕值5的对象包装器,而在这一行之后,第5行保存了基本值6。

five.wtf
five.wtf = 'potato?'
five.wtf

Five不再是宾语。每一行都创建一个新的Number实例,以便解析.wtf属性访问。实例是独立的,因此在一个实例上设置属性在另一个实例上是不可见的。代码完全等价于下面的代码:

(new Number(6)).wtf;
(new Number(6)).wtf = 'potato?';
(new Number(6)).wtf;

声明函数dis。函数返回其上下文

function dis() { return this }
undefined

用context 5调用dis。原始值在严格模式(MDN)中作为上下文传递时被装箱。所以5现在是object(盒装数字)

five = dis.call(5)
Number {[[PrimitiveValue]]: 5}

在五个变量上声明wtf属性

five.wtf = 'potato'
"potato"

5 .wtf的值

five.wtf
"potato"

5被框5,所以它同时是数字和对象(5 * 5 = 25)。它不会改变5。

five * 5
25

5 .wtf的值

five.wtf
"potato"

在这里打开5。5现在只是一个原始数。它输出5,然后5加1。

five++
5

5现在是原数6,它没有任何属性。

five.wtf
undefined

基本类型不能有属性,你不能设置这个

five.wtf = 'potato?'
"potato?"

你不能读这个,因为没有设置

five.wtf
undefined

5是6,因为上面的增量

five
6