最近的一条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 =土豆?'赋值显然设置了值。


当前回答

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

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;

其他回答

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

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;

首先,它看起来是通过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对象和基本数之间的区别。

表示数字有两种不同的方式:

var a = 5;
var b = new Number(5);

第一个是原语,第二个是对象。从所有的意图和目的来看,它们的行为是相同的,除了它们在打印到控制台时看起来不同。一个重要的区别是,作为一个对象,new Number(5)像普通{}一样接受新属性,而原语5则不接受:

a.foo = 'bar';  // doesn't stick
b.foo = 'bar';  // sticks

至于初始的dis.call(5)部分,请参阅“this”关键字如何工作?假设要调用的第一个参数被用作this的值,并且该操作将数字强制转换为更复杂的number对象形式。*稍后++会强制它回到原语形式,因为加法操作+会产生一个新的原语。

> five = dis.call(5)  // for all intents and purposes same as new Number(5)
Number {[[PrimitiveValue]]: 5}
> five.wtf = 'potato'
"potato"
> five.wtf
"potato"

Number对象接受新的属性。

> five++

++的结果是一个新的原语6值…

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

...它不具有也不接受自定义属性。

*注意,在严格模式下,this参数将被区别对待,不会被转换为Number。参见http://es5.github.io/#x10.4.3了解实现细节。

原始值不能有属性。但是,当您试图访问原始值上的属性时,它会透明地转换为临时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

JavaScript作用域由执行上下文组成。每个执行上下文都有一个词法环境(外部/全局作用域值)、一个变量环境(局部作用域值)和一个this绑定。

这个绑定是执行上下文的一个非常重要的部分。使用调用是改变这个绑定的一种方法,这样做将自动创建一个对象来填充绑定。

Function.prototype.call() (from MDN)

语法 乐趣。调用(thisArg[, arg1[, arg2[,…]]]) thisArg 这一价值提供了对乐趣的召唤。注意,这可能不是方法所看到的实际值:如果方法是一个非严格模式代码中的函数,null和undefined将被替换为全局对象,原始值将被转换为对象。(强调我的)

一旦很明显5被转换为新的数字(5),其余的应该是相当明显的。注意,其他示例也可以工作,只要它们是基本值。

函数primitiveToObject(的){ 返回dis.call(的); } 函数dis(){返回this;} / /现有的例子 console.log (primitiveToObject (5)); / /∞ console.log (primitiveToObject (1/0)); / / bool console.log (primitiveToObject (1 > 0)); / /字符串 console.log (primitiveToObject(“hello world”); <img src="http://i.stack.imgur.com/MUyRV.png" />