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


当前回答

01 > function dis() { return this }
02 undefined
03 > five = dis.call(5)
04 Number {[[PrimitiveValue]]: 5}
05 > five.wtf = 'potato'
06 "potato"
07 > five.wtf
08 "potato"
09 > five * 5
10 25
11 > five.wtf
12 "potato"
13 > five++
14 5
15 > five.wtf
16 undefined
17 > five.wtf = 'potato?'
18 "potato?"
19 > five.wtf
20 undefined
21 > five
22 6

01声明了一个返回上下文对象的函数dis。它所代表的内容取决于您是否使用严格模式。如果函数声明为:

> function dis() { "use strict"; return this }

这在ES5规范的10.4.3节中有详细说明

如果函数代码是严格的代码,将ThisBinding设置为thisArg。 否则,如果thisArg为null或未定义,则将ThisBinding设置为全局对象。 否则,如果Type(thisArg)不是Object,将ThisBinding设置为ToObject(thisArg)。

02是函数声明的返回值。Undefined在这里应该是不言自明的。

当在原语值5的上下文中调用变量5时,用返回值dis初始化变量5。因为dis不是严格模式,所以这一行等同于调用five = Object(5)。

返回值为奇数Number {[[PrimitiveValue]]: 5},表示封装了原语值5的对象

5对象的WTF属性被赋值为'potato'

06是赋值的返回值,应该是不言自明的。

07五个对象的WTF属性正在检查中

八是五。WTF之前设置为“土豆”,它在这里返回“土豆”

5对象乘以原始值5。这与其他被相乘的对象没有什么不同,ES5规范的11.5节对此进行了解释。特别值得注意的是如何将对象转换为数值,这将在几节中介绍。

9.3,当时:

让primValue为ToPrimitive(输入参数,提示数字)。 返回,当时(primValue)。

9.1 ToPrimitive:

返回Object的默认值。通过调用对象的[[DefaultValue]]内部方法来检索对象的默认值,并传递可选提示PreferredType。内部方法[[DefaultValue]]的行为由8.12.8中所有原生ECMAScript对象的规范定义。

8月12日8 [DefaultValue]:

让valueOf是调用对象O的[[Get]]内部方法的结果,参数为valueOf。 如果IsCallable(valueOf)为真,则, 让val是调用valueOf的[[Call]]内部方法的结果,O作为this值和一个空参数列表。 如果val是一个基本值,则返回val。

这完全是一种迂回的方式,说明对象的valueOf函数被调用,该函数的返回值被用于方程中。如果你要改变valueOf函数,你可以改变操作的结果:

> five.valueOf = function () { return 10 }
undefined
> five * 5
50

10 as fives valueOf函数没有改变,它返回被包装的原语值5,因此5 * 5的结果为5 * 5,结果为25

11 5个对象的WTF属性被再次计算,尽管它从05分配时就没有改变。

12“土豆”

在5处调用后缀增量操作符,它获取数值(5,我们前面介绍过),存储值以便返回,在值(6)上加上1,将值赋给5,然后返回存储值(5)

14和前面一样,返回值是它被加一之前的值

15访问存储在变量5上的原语值(6)的WTF属性。ES5规范的15.7.5节定义了这种行为。数字从Number.prototype中获取属性。

16号。Prototype没有WTF属性,因此返回undefined

17 5。WTF被赋值为'potato?'。赋值在ES5规范的11.13.1中定义。基本上,赋值会被返回,但不会被存储。

18“土豆吗?'由赋值操作符返回

19再次访问值为6的5,并再次访问Number。Prototype没有WTF属性

20未定义如上所述

21五被访问

22返回6,如13所述

其他回答

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" />

这很简单。

function dis () { return this; }

这将返回This上下文。因此,如果调用(5),则将数字作为对象传递。

call函数不提供参数,你给出的第一个参数是this的上下文。通常,如果你想要它在context上,你给它{}diss。call({})这意味着函数中的this是空的this。然而,如果你传递5,它似乎会被转换成一个对象。看到打电话给

返回的是object

当执行5 * 5时,JavaScript将对象5视为基本类型,因此相当于5 * 5。有趣的是,执行'5' * 5,它仍然等于25,因此JavaScript显然是在底层进行强制转换。在这一行中没有对底层的5类型进行任何更改

但是当你执行++时,它会将对象转换为原始数字类型,从而删除.wtf属性。因为您正在影响底层类型

原始值不能有属性。但是,当您试图访问原始值上的属性时,它会透明地转换为临时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世界里有强制——一个侦探故事

内森,你不知道你发现了什么。

我已经调查了好几周了。这一切都始于去年十月的一个暴风雨之夜。我偶然发现了Number类——我的意思是,为什么JavaScript会有一个Number类呢?

我对接下来要发现的事情毫无准备。

事实证明,JavaScript在不告诉你的情况下,已经在你眼皮底下把你的数字变成对象,把对象变成数字。

JavaScript希望没有人会发现,但人们已经报告了奇怪的意想不到的行为,现在多亏了你和你的问题,我有了我需要的证据来揭露这件事。

这是我们目前所发现的。我不知道我是否应该告诉你这个——你可能想要关闭你的JavaScript。

> function dis() { return this }
undefined

当你创建这个函数时,你可能不知道接下来会发生什么。一切看起来都很好,目前一切都很好。

没有错误消息,控制台输出中只有“未定义”一词,这正是您所期望的。毕竟,这是一个函数声明——它不应该返回任何东西。

但这仅仅是个开始。接下来发生的事,谁也没有预料到。

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

是的,我知道,你期待的是5分,但这不是你得到的,是吗?你得到了别的东西,不同的东西。

同样的事情也发生在我身上。

我不知道这是怎么回事。我都快疯了。我睡不着,吃不下,我试着把它喝掉,但再多的激浪也不能让我忘记。这完全说不通!

就在那时,我发现了真正发生的事情——这是胁迫,它就发生在我眼前,但我太瞎了,看不到它。

Mozilla试图把它埋藏在他们知道没有人会看到的地方——他们的文档。

经过数小时的反复阅读,我发现了这个:

“…原始值将被转换为对象。”

它就在那里,可以用Open Sans字体拼写出来。它是call()函数-我怎么能这么愚蠢?!

我的号码已经不再是一个号码了。当我将它传递给call()时,它变成了其他东西。它变成了……一个对象。

一开始我简直不敢相信。这怎么可能呢?但我不能忽视周围越来越多的证据。如果你仔细看,它就在那里:

> five.wtf = 'potato'
"potato"

> five.wtf
"potato"

WTF是对的。数字不能有自定义属性——我们都知道!这是他们在警校教你的第一件事。

我们应该在看到控制台输出的那一刻就知道——这不是我们认为的数字。这是一个冒名顶替者——一个把自己冒充成我们可爱的无辜数字的物体。

这是……(5)新号码。

当然!这完全说得通。Call()有工作要做,他必须调用一个函数,要做到这一点,他需要填充这个,他知道他不能用一个数字来做——他需要一个对象,他愿意做任何事情来得到它,即使这意味着强制我们的数字。当call()看到数字5时,他看到了一个机会。

这是一个完美的计划:等到没人注意的时候,把我们的号码换成一个看起来和它一模一样的东西。我们得到一个数字,函数被调用,没有人会知道。

这真是个完美的计划,但就像所有的计划一样,即使是完美的计划,上面有个洞,我们马上就要掉进去了。

看,call()不明白的是,他不是镇上唯一一个可以胁迫数字的人。这毕竟是JavaScript——强制无处不在。

call()拿走了我的号码,直到我把他的小冒名顶替者的面具拉下来,并将他暴露给整个Stack Overflow社区,我才会停止。

但如何?我需要一个计划。当然它看起来像一个数字,但我知道它不是,一定有办法证明这一点。就是这样!它看起来像一个数字,但它能像一个数字吗?

我告诉五个人我需要他变大五倍——他没有问为什么,我也没有解释。然后我做了任何优秀程序员都会做的事情:相乘。当然,他不可能通过造假来摆脱这一切。

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

该死的!不仅5乘起来很好wtf还在那里。该死的家伙和他的土豆。

到底发生了什么事?我错了吗?五真的是个数字吗?不,我一定遗漏了什么,我知道,我一定忘记了什么,一些如此简单和基本的东西,以至于我完全忽略了它。

这看起来不太好,我已经写了几个小时的答案,但我仍然没有更接近我的观点。我不能再坚持下去了,最终人们会停止阅读,我必须想出点什么,而且必须要快。

等等,就是这个!5不是25,25是结果,25是一个完全不同的数字。当然,我怎么会忘记呢?数字是不可变的。当你用5乘5的时候,什么都没有分配给任何东西你只是创建了一个新的数字25。

这肯定就是这里发生的事情。当我用5乘5的时候,5一定会变成一个数字这个数字一定是用来做乘法的。输出到控制台的是乘法运算的结果,而不是5本身的值。5没有分配到任何东西,所以它当然不会改变。

那么我如何让5把操作的结果赋给自己呢?我明白了。五点还没来得及思考,我就大喊“++”。

> five++
5

啊哈!我抓到他了!每个人都知道5 + 1 = 6,这就是我需要证明5不是数字的证据!那是个骗子!一个不会数数的骗子。我可以证明这一点。实数是这样的:

> num = 5
5
> num++
5

等待?这里发生了什么?唉,我太忙了,都忘了后期操作员是怎么工作的了。当我在5的末尾使用++时,我说的是返回当前值,然后增加5。它是将操作发生之前的值打印到控制台。num实际上是6,我可以证明它:

>num
6

是时候看看5到底是什么了:

>five
6

...这正是它应该有的样子。5个不错,但我比他更好。如果5仍然是一个对象,这意味着它仍然具有wtf属性,我愿意赌它没有的一切。

> five.wtf
undefined

啊哈!我是对的。我抓到他了!五现在是一个数字,不再是一个物体了。我知道乘法技巧这次救不了我。5 ++实际上是5 = 5 + 1。与乘法运算不同,++运算符将值赋给5。更具体地说,它将5 + 1的结果赋给它就像在乘法的情况下返回一个新的不可变数。

我知道他已经在我手上了,为了确保他逃不掉。我还有一个测试要做。如果我是对的,5现在真的是一个数字,那么这就行不通了:

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

这次他不会再骗我了。我知道土豆?会被打印到控制台因为那是赋值的输出。真正的问题是,wtf还会在那里吗?

> five.wtf
undefined

正如我怀疑的那样——什么都没有——因为数字不能被赋予属性。我们在学院的第一年就知道了;)

谢谢内森。多亏你有勇气问我这个问题,我终于可以把这一切抛在脑后,开始一个新的案子。

就像这个关于toValue()函数的例子。哦,天哪。人参公鸡!

01 > function dis() { return this }
02 undefined
03 > five = dis.call(5)
04 Number {[[PrimitiveValue]]: 5}
05 > five.wtf = 'potato'
06 "potato"
07 > five.wtf
08 "potato"
09 > five * 5
10 25
11 > five.wtf
12 "potato"
13 > five++
14 5
15 > five.wtf
16 undefined
17 > five.wtf = 'potato?'
18 "potato?"
19 > five.wtf
20 undefined
21 > five
22 6

01声明了一个返回上下文对象的函数dis。它所代表的内容取决于您是否使用严格模式。如果函数声明为:

> function dis() { "use strict"; return this }

这在ES5规范的10.4.3节中有详细说明

如果函数代码是严格的代码,将ThisBinding设置为thisArg。 否则,如果thisArg为null或未定义,则将ThisBinding设置为全局对象。 否则,如果Type(thisArg)不是Object,将ThisBinding设置为ToObject(thisArg)。

02是函数声明的返回值。Undefined在这里应该是不言自明的。

当在原语值5的上下文中调用变量5时,用返回值dis初始化变量5。因为dis不是严格模式,所以这一行等同于调用five = Object(5)。

返回值为奇数Number {[[PrimitiveValue]]: 5},表示封装了原语值5的对象

5对象的WTF属性被赋值为'potato'

06是赋值的返回值,应该是不言自明的。

07五个对象的WTF属性正在检查中

八是五。WTF之前设置为“土豆”,它在这里返回“土豆”

5对象乘以原始值5。这与其他被相乘的对象没有什么不同,ES5规范的11.5节对此进行了解释。特别值得注意的是如何将对象转换为数值,这将在几节中介绍。

9.3,当时:

让primValue为ToPrimitive(输入参数,提示数字)。 返回,当时(primValue)。

9.1 ToPrimitive:

返回Object的默认值。通过调用对象的[[DefaultValue]]内部方法来检索对象的默认值,并传递可选提示PreferredType。内部方法[[DefaultValue]]的行为由8.12.8中所有原生ECMAScript对象的规范定义。

8月12日8 [DefaultValue]:

让valueOf是调用对象O的[[Get]]内部方法的结果,参数为valueOf。 如果IsCallable(valueOf)为真,则, 让val是调用valueOf的[[Call]]内部方法的结果,O作为this值和一个空参数列表。 如果val是一个基本值,则返回val。

这完全是一种迂回的方式,说明对象的valueOf函数被调用,该函数的返回值被用于方程中。如果你要改变valueOf函数,你可以改变操作的结果:

> five.valueOf = function () { return 10 }
undefined
> five * 5
50

10 as fives valueOf函数没有改变,它返回被包装的原语值5,因此5 * 5的结果为5 * 5,结果为25

11 5个对象的WTF属性被再次计算,尽管它从05分配时就没有改变。

12“土豆”

在5处调用后缀增量操作符,它获取数值(5,我们前面介绍过),存储值以便返回,在值(6)上加上1,将值赋给5,然后返回存储值(5)

14和前面一样,返回值是它被加一之前的值

15访问存储在变量5上的原语值(6)的WTF属性。ES5规范的15.7.5节定义了这种行为。数字从Number.prototype中获取属性。

16号。Prototype没有WTF属性,因此返回undefined

17 5。WTF被赋值为'potato?'。赋值在ES5规范的11.13.1中定义。基本上,赋值会被返回,但不会被存储。

18“土豆吗?'由赋值操作符返回

19再次访问值为6的5,并再次访问Number。Prototype没有WTF属性

20未定义如上所述

21五被访问

22返回6,如13所述