在这个代码中发生了什么事情,Number对象持有属性并递增数字?
最近的一则鸣叫包含了这段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]]
属性的数字,但five++
和five * 5
看起来只是普通数字5
和25
(不是Number
s) - 为什么
five.wtf
属性在five++
增量后消失 - 为什么
five.wtf
属性在five++
增量之后不再可设置,尽pipe有five.wtf = 'potato?'
显然设定的价值。
OP在这里。 有趣的看到这堆栈溢出:)
在介绍行为之前,重要的是澄清一些事情:
-
数值和数字对象 (
a = 3
vsa = new Number(3)
)是非常不同的。 一个是原始的,另一个是对象。 您不能将属性分配给基元,但可以将其分配给对象。 -
两者之间的强制是隐含的。
例如:
(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
-
每个expression式都有一个返回值。 当REPL读取并执行一个expression式时,这就是它显示的内容。 返回值通常并不意味着你的想法和暗示的事情是不正确的。
好的,我们走吧。
誓言。
> function dis() { return this } undefined > five = dis.call(5) [Number: 5]
定义一个函数dis
并用5
调用它。 这将执行5
作为上下文( this
)的function。 在这里它被从一个数字值强制转换为一个数字对象。 这是非常重要的,要注意的是,我们在严格的模式, 这不会发生 。
> five.wtf = 'potato' 'potato' > five.wtf 'potato'
现在我们把属性five.wtf
设置为'potato'
,用五个作为对象,当然它接受简单分配 。
> five * 5 25 > five.wtf 'potato'
用five
作为对象,我确保它仍然可以执行简单的算术运算。 它可以。 它的属性仍然坚持? 是。
轮到了。
> five++ 5 > five.wtf undefined
现在我们检查five++
。 后缀增量的技巧是整个expression式将评估原始值,然后增加值。 看起来five
还是五个,但是真正的performance评价为五个,然后是five
到6
。
five
不仅被设置为6
,而且被强制回到一个数字值,所有的属性都丢失了。 由于原语不能保存属性,因此five.wtf
是未定义的。
> five.wtf = 'potato?' 'potato?' > five.wtf undefined
我再次试图重新分配一个属性wtf
到five
。 返回值意味着它坚持,但实际上并不是因为five
是一个数值,而不是一个数字对象。 这个expression评价为'potato?'
,但是当我们检查我们看到它没有分配。
威望。
> five 6
自从后缀增量以来, five
已经是6
。
有两种不同的方法来表示一个数字:
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”这个关键字是如何工作的? 。 我们只需要说第一个call
参数被用作这个值,而且这个操作会把这个数字强制为更复杂的Number
对象forms。*稍后++
强制它回到原始forms,因为加法操作+
导致一个新的原始。
> 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 。
JavaScript世界中的强制 – 侦探故事
纳森,你不知道你发现了什么。
我已经调查了几个星期了。 这一切都是在去年十月的暴风雨之夜开始的。 我意外地偶然发现了Number
类 – 我的意思是,为什么在JavaScript中有一个Number
类?
我没有准备好接下来要发现的内容。
事实certificate,没有告诉你,JavaScript已经改变了你的号码,把对象和你的对象改变成你鼻子下面的号码。
JavaScript希望没有人会接受,但人们一直在报告奇怪的意外行为,现在感谢你和你的问题,我有我需要的证据来打开这个事情。
这是我们迄今为止发现的。 我不知道我是否应该告诉你这一点 – 你可能想closures你的JavaScript。
> function dis() { return this } undefined
当你创build这个函数时,你可能不知道接下来会发生什么。 一切都很好,现在一切都很好。
没有错误消息,只是在控制台输出中的“未定义”一词,正是你所期望的。 毕竟,这是一个函数声明 – 它不应该返回任何东西。
但这只是一个开始。 接下来发生的事情,谁都没有预料到。
> five = dis.call(5) Number {[[PrimitiveValue]]: 5}
是的,我知道,你预计5
,但这不是你得到的,是吗 – 你有别的东西 – 有些不同。
这样的事情我也经历过。
我不知道该怎么做。 这让我疯狂。 我睡不着,我不能吃,我试着把它喝掉,但没有任何的山露会让我忘记。 这只是没有任何意义!
那时候我才知道发生了什么事情 – 这是强制性的,发生在我眼前,但是我太盲目了。
Mozilla试图把它放在他们知道没有人会看的地方 – 他们的文档 。
经过几个小时的recursion阅读,重新阅读和重新阅读,我发现这个:
“…原始值将被转换为对象。”
这是正确的,可以用Open Sans字体拼出来。 这是call()
函数 – 我怎么会这么愚蠢?!
我的电话号码不再是一个号码。 我把它传递给call()
的那一刻,变成了别的东西。 它变成了一个对象。
起初我不敢相信。 这怎么可能是真的? 但我无法忽视周围的证据。 如果你只是看:
> five.wtf = 'potato' "potato" > five.wtf "potato"
wtf
是对的。 数字不能有自定义属性 – 我们都知道! 这是他们在学院教你的第一件事。
我们应该知道我们看到控制台输出的那一刻 – 这不是我们认为的数字。 这是一个骗子 – 一个物体,作为我们甜美无辜的数字。
这是… new Number(5)
。
当然! 这非常有意义。 call()
有一个工作要做,他不得不调用一个函数,为此他需要填充this
,他知道他不能用一个数字来做到这一点 – 他需要一个对象,他愿意做任何事情得到它,即使这意味着强迫我们的数字。 当call()
看到数字5
,他看到了一个机会。
这是一个完美的计划:等到没有人看,把我们的号码换成一个看起来就像它的物体。 我们得到一个数字,函数被调用,没有人会更聪明。
这确实是一个完美的计划,但是就像所有的计划,甚至是完美的计划一样,它里面有一个漏洞,我们即将陷入困境。
看,什么call()
不明白的是,他不是城里唯一可以强制数字的人。 毕竟这是JavaScript – 强制无处不在。
call()
把我的号码,我不会停止,直到我把他的小骗子的面具,并暴露给整个堆栈溢出社区。
但是,如何? 我需要一个计划。 当然,它看起来像一个数字,但我知道它不是,有一种方法来certificate这一点。 而已! 它看起来像一个数字,但它可以像一个?
我告诉five
我需要他成为5倍 – 他没有问为什么,我没有解释。 然后,我做了一个好程序员会做的事情:我成倍增长。 当然,他没有办法摆脱这一切。
> five * 5 25 > five.wtf 'potato'
该死的! 不仅five
只繁殖蛮好的还在那里。 该死的人和他的马铃薯。
到底是怎么回事? 我是否错了这件事? five
真的是一个数字吗? 不,我必须错过一些东西,我知道,有一些我必须遗忘,一些如此简单和基本的东西,我完全忽略了它。
这看起来不太好,我已经写了几个小时的这个答案,我还没有接近我的观点。 我不能保持这种状态,最终人们会停止阅读,我不得不想一些事情,我必须快速思考。
等等就是这样! five
不是25,25是结果,25是一个完全不同的数字。 当然,我怎么能忘记? 数字是不可变的。 当你乘以5 * 5
什么都没有分配给任何东西,你只是创build一个新的数字25
。
这一定是这里发生的事情。 不知何故,当我乘以five * 5
, five
必须被强制为一个数字,这个数字必须是用于乘法的数字。 这是打印到控制台的乘法结果,而不是five
本身的值。 five
从来没有分配任何东西 – 所以当然不会改变。
那么我怎样才能得到five
分配自己的手术的结果。 我知道了。 在five
还没有机会思考之前,我喊了一声“++”。
> five++ 5
啊哈! 我有他! 每个人都知道5 + 1
是6
,这是我需要揭露的five
证据不是数字的证据! 这是一个骗子! 一个坏的冒名顶替者,不知道如何计数。 我可以certificate这一点。 以下是一个真实的数字如何行事:
> num = 5 5 > num++ 5
等待? 这里发生了什么? 叹了口气,我陷入了困境,我忘记了邮政经营者的工作方式。 当我在five
的末尾使用++
时,我说的是返回当前值,然后递增five
。 这是操作发生之前的值被打印到控制台。 实际上6
我可以certificate的:
>num 6
时间看看真正的five
是什么:
>five 6
这正是它应该的。 five
是好的 – 但我更好。 如果five
仍然是一个对象,这意味着它仍然有财产wtf
,我愿意赌一切它没有。
> five.wtf undefined
啊哈! 我是对的。 我有他! five
是现在的数字 – 这不再是一个对象。 我知道乘法技巧这次不会挽救它。 看five++
实际上是five = five + 1
。 与乘法不同, ++
运算符将值赋给five
。 更具体地说,它指定了five + 1
的结果,就像在乘法的情况下一样,返回一个新的不变的数字 。
我知道我有他,只是为了确保他无法摆脱它。 我还有一个testing了我的袖子。 如果我是对的,现在five
确实是一个数字,那么这是行不通的。
> five.wtf = 'potato?' 'potato?'
这次他不会骗我的。 我知道potato?
将被打印到控制台,因为这是作业的输出。 真正的问题是, wtf
还会在那里吗?
> five.wtf undefined
正如我怀疑 – 没有 – 因为数字不能被分配属性。 我们了解到学院的第一年;)
谢谢Nathan。 感谢你们提出这个问题的勇气,我终于可以把这一切都放在我身后,并转向一个新的案例。
像这个关于函数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
。 this
代表的变化取决于你是否使用严格模式。 如果函数被声明为:整个示例有不同的结果:
> function dis() { "use strict"; return this }
这在ES5规范的第10.4.3节中有详细说明
- 如果函数代码是严格代码,则将ThisBinding设置为thisArg。
- 否则,如果thisArg为null或未定义,请将ThisBinding设置为全局对象。
- 否则,如果Type(thisArg)不是Object,则将ThisBinding设置为ToObject(thisArg)。
02
是函数声明的返回值。 undefined
在这里应该是自我解释的。
03
在原始值5
的上下文中调用时,variablesfive
被初始化为dis
的返回值。 因为dis
不是严格模式,所以这一行和调用five = Object(5)
是一样的。
04
奇数Number {[[PrimitiveValue]]: 5}
返回值是包装原始值5
的对象的表示
05
five
对象的wtf
属性被分配一个string值'potato'
06
是作业的返回值,应该是自我解释的。
07
正在检查five
对象的wtf
属性
08
as five.wtf
先前被设置为'potato'
,这里返回'potato'
09
five
对象被乘以原始值5
。 这与其他任何对象相乘并没有什么不同,在ES5规范的第11.5节中有解释。 特别值得注意的是对象如何转换为数值,这在几个小节中已经介绍过了。
9.3 ToNumber :
- 设primValue为ToPrimitive(input参数,提示数字)。
- 返回ToNumber(primValue)。
9.1原创 :
返回对象的默认值。 通过调用对象的[[DefaultValue]]内部方法来检索对象的默认值,并传递可选的提示PreferredType。 本规范针对8.12.8中的所有本地ECMAScript对象定义了[[DefaultValue]]内部方法的行为。
8.12.8 [[DefaultValue]] :
让valueOf是用参数“valueOf”调用对象O的[[Get]]内部方法的结果。
如果IsCallable(valueOf)为true,那么,
- 让val是调用valueOf的[[Call]]内部方法的结果,其中O为此值和一个空的参数列表。
- 如果val是原始值,则返回val。
这是一个迂回的说法,这个对象的valueOf
函数被调用,并且这个函数的返回值被用在方程中。 如果您要更改valueOf
函数,则可以更改操作的结果:
> five.valueOf = function () { return 10 } undefined > five * 5 50
10
作为five
valueOf
函数是不变的,它返回包装的原始值5
以便five * 5
评估到5 * 5
,结果25
11
five
对象的wtf
属性再次被评估,尽pipe与05
被分配的时候没有变化。
12
'potato'
13
后缀增量运算符被调用five
,得到数值( 5
,我们介绍了如何更早),存储的价值,使它可以返回,加1
到值( 6
),赋值为five
,和返回存储的值( 5
)
如前所述,返回的值是增加前的值
存储在variablesfive
中的原始值( 6
)的wtf
属性被访问。 ES5规范的第15.7.5节定义了这种行为。 Numbers从Number.prototype
获取属性。
Number.prototype
没有一个wtf
属性,所以返回undefined
17
five.wtf
被分配了一个'potato?'
的值。 。 分配在ES5规范的11.13.1中定义 。 基本上分配的值是返回,但不存储。
18
'potato?'
被赋值运算符返回
19
再次five
,其值为6
被访问,并且Number.prototype
没有一个wtf
属性
20
如上所述undefined
21
five
访问
如13
返回22
6
这很简单。
function dis () { return this; }
这返回this
上下文。 所以,如果你call(5)
你传递的数字作为一个对象。
call
函数不提供参数,你给的第一个参数是这个的上下文。 通常情况下,如果你想在上下文中使用它,你可以使用dis.call({})
,这意味着在函数中this
是一个空的。 但是,如果你传递5
它似乎将被转换为一个对象。 参见.call
所以回报是object
当你做five * 5
,JavaScript将对象five
看作原始types,所以相当于5 * 5
。 有趣的是, '5' * 5
,它仍然等于25
,所以JavaScript显然是在引擎盖下。 这一行没有对底层的five
types进行更改
但是,当你做++
它会将对象转换为原始number
types,从而删除.wtf
属性。 因为你正在影响基础types
原始值不能有属性。 但是,当您尝试访问原始值上的属性时,它会透明地转换为临时Number对象。
所以:
> 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
声明函数dis
。 函数返回其上下文
function dis() { return this } undefined
调用与上下文5
。 在严格模式( MDN )中作为上下文传递时,原始值被装箱。 所以five
现在是对象(盒装数字)。
five = dis.call(5) Number {[[PrimitiveValue]]: 5}
声明five
variables的wtf
属性
five.wtf = 'potato' "potato"
five.wtf
值
five.wtf "potato"
five
是盒装5
,所以它的数量和对象在同一时间(5 * 5 = 25)。 它不会改变five
。
five * 5 25
five.wtf
值
five.wtf "potato"
在这里拆箱five
five
现在只是原始的number
。 它打印5
,然后添加1
到five
。
five++ 5
five
是现在的原始数字6
,没有任何属性。
five.wtf undefined
基元不能有属性,你不能设置这个
five.wtf = 'potato?' "potato?"
你不能读这个,因为它没有设置
five.wtf undefined
five
是因为岗位递增以上
five 6
首先,它看起来像是通过nodejs控制台运行。
1。
function dis() { return this }
创build函数dis(),但是因为它没有被设置为var
所以没有值返回,因此即使dis()
被定义,输出也是未定义的。 在旁注中, this
没有被返回,因为该函数没有被执行。
2。
five = dis.call(5)
这将返回javascript的Number
对象,因为您只需将函数dis()
的this
值设置为基元5。
3。
five.wtf = 'potato'
第一个返回"potato"
因为你只是将属性wtf
设置为'potato'
。 Javascript返回你设置的variables的值,可以很容易地链接多个variables,并将它们设置为相同的值,如下所示: a = b = c = 2
。
4。
five * 5
这会返回25
因为您只是将原始数字5
乘以five
。 five
的值是由Number
对象的值决定的。
5。
five.wtf
之前我跳过这一行,因为我会在这里重复它。 它只是返回上面设置的属性wtf
的值。
6。
five++
正如@Callum所说的, ++
会将types转换为对象Number {[[PrimitiveValue]]: 5}}
相同的值。
现在因为five
是一个number
,你不能再设置属性,直到你做这样的事情:
five = dis.call(five) five.wtf = "potato?"
要么
five = { value: 6, wtf: "potato?" }
另请注意,第二种方法与使用第一种方法的行为不同,因为它定义了一个通用对象,而不是之前创build的Number
对象。
我希望这可以帮助,JavaScript喜欢假设的事情,所以当从Number
对象改变为原始number
时,可能会变得混乱。 您可以使用typeof
关键字来检查是什么types的东西,在初始化它之后写入typeof返回'object'
,并且在您执行five++
后返回'number'
。
@deceze非常好地描述了Number对象和原始数字之间的区别。
JavaScript范围由执行上下文组成。 每个执行上下文都有一个词法环境(外部/全局范围的值),一个variables环境(本地范围的值)和一个这个绑定 。
这个绑定是执行上下文中非常重要的一部分。 使用call
是一种方法来改变这个绑定 ,这样做会自动创build一个对象来填充绑定。
Function.prototype.call() (来自MDN)
句法
fun.call(thisArg[, arg1[, arg2[, ...]]])
thisArg
这个提供给这个调用fun的值。 请注意,这可能不是该方法看到的实际值:如果该方法是非严格模式代码中的函数,则将使用全局对象replacenull和undefined,并将原始值转换为对象 。 (重点是我的)
一旦显示5被转换成new Number(5)
,其余的应该是相当明显的。 请注意,只要它们是原始值,其他示例也将起作用。
function primitiveToObject(prim){ return dis.call(prim); } function dis(){ return this; } //existing example console.log(primitiveToObject(5)); //Infinity console.log(primitiveToObject(1/0)); //bool console.log(primitiveToObject(1>0)); //string console.log(primitiveToObject("hello world"));
<img src="http://i.stack.imgur.com/MUyRV.png" />
几个概念解释发生了什么
5
是一个数字,一个原始值
Number {[[PrimitiveValue]]: 5}
是Number的一个实例(让我们称之为对象包装器)
每当你访问一个原始值的属性/方法时,JS引擎将创build一个适当types的对象包装器( Number
for 5
, String
'str'
, Boolean
for true
),parsing对象的属性access / method调用包装。 例如,当您执行true.toString()
时会发生这种情况。
对对象执行操作时,它们被转换为原始值(通过使用toString
或valueOf
)以解决这些操作 – 例如,在执行
var obj = { a : 1 }; var string = 'mystr' + obj; var number = 3 + obj;
string
将持有mystr
和obj.toString()
的string连接,并且number
将保持3
和obj.valueOf()
的加法。
现在把它放在一起
five = dis.call(5)
dis.call(5)
行为就像(5).dis()
如果5
实际上有dis
方法。 为了解决方法调用,将创build对象包装器,并在其上parsing方法调用。 此时五点指向原始值5的对象包装。
five.wtf = 'potato'
在一个对象上设置一个属性,这里没有什么特别的。
five * 5
这实际上是five.valueOf() * 5
从对象包装器获取原始值。 five
仍然指向最初的对象。
five++
这实际上是five = five.valueOf() + 1
。 在这行之前five
包含值5的对象包装,而此后five
持有原始值6
。
five.wtf five.wtf = 'potato?' five.wtf
five
不再是一个对象。 每行都创build一个Number的新实例,以便parsing.wtf
属性访问。 这些实例是独立的,因此在其中设置属性在另一个上不可见。 代码完全等同于这个:
(new Number(6)).wtf; (new Number(6)).wtf = 'potato?'; (new Number(6)).wtf;