使用Number对象保存属性并递增数字,此代码中发生了什么?

Nat*_*ong 247 javascript

最近的一条推文包含了这段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
Run Code Online (Sandbox Code Playgroud)

特别是,我不清楚:

  • 为什么结果dis.call(5)Number带有某种[[PrimitiveValue]]属性的,但结果five++five * 5看起来只是普通的数字525(不是Numbers)
  • 为什么five.wtf属性在five++增量后消失
  • 为什么five.wtf属性在five++增量后不再可five.wtf = 'potato?'设置,尽管分配显然设置了值.

Mat*_*hew 278

OP在这里.有趣的是在Stack Overflow上看到这个:)

在逐步完成这一行为之前,澄清一些事情很重要:

  1. 数值数字对象(a = 3vs a = new Number(3))是非常不同的.一个是原始的,另一个是对象.您不能将属性分配给基元,但可以分配给对象.

  2. 两者之间的强制是隐含的.

    例如:

    (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
    
    Run Code Online (Sandbox Code Playgroud)
  3. 每个Expression都有一个返回值.当REPL读取并执行表达式时,这就是它显示的内容.返回值通常并不意味着你的想法,而是暗示那些不正确的事情.

好的,我们走了.

JavaScript代码的原始图像

誓言.

> function dis() { return this }
undefined
> five = dis.call(5)
[Number: 5]
Run Code Online (Sandbox Code Playgroud)

定义一个函数dis调用5.这将使用5context(this)执行该函数.这里它是从Number值强制转换为Number对象.值得注意的是,如果我们处于严格模式, 那就不会发生这种情况.

> five.wtf = 'potato'
'potato'
> five.wtf
'potato'
Run Code Online (Sandbox Code Playgroud)

现在我们将属性设置five.wtf'potato',并将五个作为对象,确定它接受简单分配.

> five * 5
25
> five.wtf
'potato'
Run Code Online (Sandbox Code Playgroud)

随着five作为对象,我保证它仍然可以进行简单的算术运算.它可以.它的属性还坚持吗?是.

转弯.

> five++
5
> five.wtf
undefined
Run Code Online (Sandbox Code Playgroud)

现在我们检查five++.后缀增量的技巧是整个表达式将根据原始值进行评估,然后递增值.它看起来five仍然是五,但实际上表达式评估为五,然后设置five6.

不仅five设置为6,而且它被强制转换为Number值,并且所有属性都丢失了.由于原语不能保存属性,five.wtf因此未定义.

> five.wtf = 'potato?'
'potato?'
> five.wtf
undefined
Run Code Online (Sandbox Code Playgroud)

我再次尝试将属性重新分配wtffive.返回值意味着它坚持,但它实际上并不是因为five是Number值,而不是Number对象.表达式求值为'potato?',但是当我们检查时,我们看到它没有被分配.

声望.

> five
6
Run Code Online (Sandbox Code Playgroud)

自从postfix增量以来,five一直如此6.

  • 你在仔细观察吗? (70认同)
  • @Eric第一件事[`++`做的是将ToNumber应用于该值](http://www.ecma-international.org/ecma-262/6.0/#sec-postfix-increment-operator-runtime-semantics-评价).比较类似的情况和字符串:如果你有`x ="5"`,那么`x ++`返回数字`5`. (4认同)
  • @Nathan Long嗯,在Java中,JavaScript从一开始就大量借用,所有原语都有一个等价的类.例如,`int`和`Integer`.我假设这是你可以创建一个函数`doSomething(Object)`,并且仍然可以给它原语.在这种情况下,基元将转换为相应的类.但是JS并不真正关心类型,所以原因可能是其他原因 (3认同)
  • @gman除了有更多更详细的影响,包括标准库的大块命名,标准对象类型的行为,甚至它使用括号和分号语法的事实(即使分号是可选的)源自Java程序员熟悉的事实.是的,这对初学者来说很困惑.这并不意味着不存在影响. (3认同)
  • 真的,所有这些都发生在`dis.call(5)`,强制反对,我永远不会想到这一点. (2认同)
  • @NathanLong设计"为什么":对原语的所有操作都是在`type`的相应函数原型上定义的.这允许它包装所有`Number`s,`+/- Infinity`和`NaN`的行为.历史性的"为什么":这个实现模仿Java的[Autoboxing and Unboxing](https://docs.oracle.com/javase/tutorial/java/data/autoboxing.html),尽管这两种语言基本上都是**非常不一样. (2认同)
  • 具有相似的名称并不能阻止它们相关.JavaScript显然受到Java的影响,而且两者都受到了C. Java在2004年之前没有自动装箱,但是在创建JavaScript时它确实具有原始与对象的区别.请参阅:https://brendaneich.com/2008/04/popularity/ (2认同)

dec*_*eze 78

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

var a = 5;
var b = new Number(5);
Run Code Online (Sandbox Code Playgroud)

第一个是基元,第二个是对象.对于所有意图和目的,两者都表现相同,除非它们在打印到控制台时看起来不同.一个重要的区别是,作为一个对象,new Number(5)接受新属性就像任何普通的{},而原始的5不是:

a.foo = 'bar';  // doesn't stick
b.foo = 'bar';  // sticks
Run Code Online (Sandbox Code Playgroud)

至于初始dis.call(5)部分,请参阅"this"关键字如何工作?.让我们说第一个参数将call被用作值的值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"
Run Code Online (Sandbox Code Playgroud)

Number对象接受新的属性.

> five++
Run Code Online (Sandbox Code Playgroud)

++产生一个新的原始6价值......

> five.wtf
undefined
> five.wtf = 'potato?'
"potato?"
> five.wtf
undefined
Run Code Online (Sandbox Code Playgroud)

...没有和不接受自定义属性.

*请注意,在严格模式下,this参数将被区别对待,并且不会转换为a Number.有关实现的详细信息,请参见http://es5.github.io/#x10.4.3.

  • @Pharap肯定在C/C++中更好,你可以做`#define true false`.或者在Java中,您可以重新定义数字的含义.这些是很好的固体语言.事实上,每种语言都有类似的"技巧",你可以得到一个按预期工作但可能看起来很奇怪的结果. (2认同)

Lui*_*rez 59

JavaScript世界中存在强制 - 一个侦探故事

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

我一直在调查这几周了.这一切都始于去年十月的暴风雨之夜.我不小心偶然发现了这个Number课程 - 我的意思是,为什么世界上有JavaScript有Number课?

我没准备好接下来要发现的东西.

事实证明,JavaScript在没有告诉你的情况下,一直在将你的数字更改为对象,将对象更改为数字.

JavaScript希望没有人会流行,但人们一直在报告奇怪的意外行为,现在感谢你和你的问题,我有证据表明我需要把这个事情吹得一团糟.

这是我们到目前为止所发现的.我不知道我是否应该告诉你 - 你可能想要关闭你的JavaScript.

> function dis() { return this }
undefined
Run Code Online (Sandbox Code Playgroud)

当您创建该功能时,您可能不知道接下来会发生什么.一切都很好看,一切都很好 - 现在.

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

但这只是一个开始.接下来发生了什么,没有人能够预测到.

> five = dis.call(5)
Number {[[PrimitiveValue]]: 5}
Run Code Online (Sandbox Code Playgroud)

是的,我知道,你期待一个5,但那不是你得到的,是它 - 你得到别的东西 - 不同的东西.

这样的事情我也经历过.

我不知道该怎么做.它让我疯了.我无法入睡,我无法进食,我试着把它喝掉,但没有多少山露会让我忘记.它没有任何意义!

就在那时我发现了真正发生的事情 - 它是强制性的,它发生在我眼前,但是我太盲目无法看到它.

Mozilla试图通过将它放在他们不知道的人的位置来掩埋它 - 他们的文档.

经过几个小时的递归阅读和重新阅读并重新阅读后,我发现了这一点:

"......原始值将转换为对象."

它是正确的,因为可以用Open Sans字体拼写出来.这是call()功能 - 我怎么会这么愚蠢?!

我的号码不再是一个数字.我传递它的那一刻call(),它变成了别的东西.它变成了......一个对象.

起初我简直不敢相信.这怎么可能是真的?但我不能忽视我周围的证据.如果你只是看,它就在那里:

> five.wtf = 'potato'
"potato"

> five.wtf
"potato"
Run Code Online (Sandbox Code Playgroud)

wtf是正确的.数字不能有自定义属性 - 我们都知道!这是他们在学院教你的第一件事.

我们应该知道我们看到控制台输出的那一刻 - 这不是我们认为的数字.这是一个冒名顶替者 - 一个将自己作为我们甜蜜的无辜号码的对象.

这是... new Number(5).

当然!它非常有意义.call()我有一份工作要做,他必须调用一个功能,为了做到这一点,他需要填充this,他知道他不能用一个数字来做 - 他需要一个物体,他愿意做任何事来得到它,甚至如果这意味着强迫我们的号码.当call()看到这个号码时5,他看到了一个机会.

这是一个完美的计划:等到没有人在寻找并换出我们的数字来寻找一个看起来像它的物体.我们得到一个数字,函数被调用,没有人会更聪明.

这真是一个完美的计划,但是就像所有计划一样,即使是完美的计划,也有一个洞,我们即将陷入其中.

看,有什么call()不明白的是,他不是镇上唯一能够强迫数字的人.毕竟这是JavaScript - 强制无处不在.

call() 拿走了我的电话号码,直到我把面具从他的小冒名顶替者身上移开并将他暴露给整个Stack Overflow社区之后我才会停下来.

但是怎么样?我需要一个计划.当然它看起来像一个数字,但我知道它不是,有必要证明这一点.而已!它看起来像一个数字,但它可以像一个?

我告诉five我需要他变大5倍 - 他没有问为什么,我没有解释.然后,我做了任何优秀的程序员会做的事情:我成倍增加.当然,他无法伪造自己的方式.

> five * 5
25
> five.wtf
'potato'
Run Code Online (Sandbox Code Playgroud)

该死的!不仅five繁殖好了wtf仍然存在.该死的这个家伙和他的土豆.

到底是怎么回事?这整件事我错了吗?是five真的多少?不,我必须遗漏一些东西,我知道,有一些我必须忘记的东西,一些如此简单和基本的东西,我完全忽视它.

这看起来不太好,我几个小时都在写这个答案,我仍然没有接近我的观点.我无法保持这种状态,最终人们会停止阅读,我不得不想一些事情,我不得不快速地想到它.

等等吧!five不是25,25是结果,25是完全不同的数字.当然,我怎么能忘记?数字是不可变的.当你乘以5 * 5任何东西时,你只需要创建一个新数字25.

那一定是这里发生的事情.不知何故,当我乘以时five * 5,five必须被强制转换为数字,并且该数字必须是用于乘法的数字.这是乘法的结果,打印到控制台,而不是five自身的值.five永远不会被分配任何东西 - 所以当然它不会改变.

那么我该如何five为自己分配一个操作的结果.我知道了.在five有机会思考之前,我大喊"++".

> five++
5
Run Code Online (Sandbox Code Playgroud)

啊哈!我有他!大家都知道5 + 1就是6,这是我需要公开的证据five是不是一个数字!这是冒名顶替者!一个不知道怎么算的坏冒名顶替者.我可以证明这一点.以下是实数的作用:

> num = 5
5
> num++
5
Run Code Online (Sandbox Code Playgroud)

等待?这里发生了什么?口气,我陷入了破坏five,我忘记了后期操作员的工作方式.当我++在最后使用时,five我说要返回当前值,然后递增five.这是操作发生之前打印到控制台的值.num事实上6,我可以证明这一点:

>num
6
Run Code Online (Sandbox Code Playgroud)

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

>five
6
Run Code Online (Sandbox Code Playgroud)

......它应该是它应该是什么.five很好 - 但我更好.如果five仍然是一个对象意味着它仍然拥有财产wtf,我愿意打赌它没有的一切.

> five.wtf
undefined
Run Code Online (Sandbox Code Playgroud)

啊哈!我是正确的.我有他!five现在是一个数字 - 它不再是一个对象了.我知道乘法技巧这次不能保存它.看到five++的确如此five = five + 1.与乘法不同,运算++符为其赋值five.更具体地说,它为其赋予结果,five + 1就像在乘法的情况下返回一个新的不可变数字一样.

我知道我有他,只是为了确保他不能摆脱它.我还有一个测试我的袖子.如果我是对的,five现在真的是一个数字,那么这将不起作用:

> five.wtf = 'potato?'
'potato?'
Run Code Online (Sandbox Code Playgroud)

这次他不会欺骗我.我知道potato?将打印到控制台,因为这是作业的输出.真正的问题是,还会wtf存在吗?

> five.wtf
undefined
Run Code Online (Sandbox Code Playgroud)

正如我怀疑 - 没有 - 因为数字不能分配属性.我们了解到学院的第一年;)

谢谢内森.由于你有勇气提出这个问题,我终于可以把所有这些都放在我身后,继续研究一个新案例.

像这个关于功能的一个toValue().哦,亲爱的上帝.拿去!

  • 算了吧杰克; 这是Javascript. (9认同)

zzz*_*Bov 28

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
Run Code Online (Sandbox Code Playgroud)

01声明一个dis返回上下文对象的函数.什么this代表更改取决于您是否使用严格模式.如果函数声明为:整个示例有不同的结果:

> function dis() { "use strict"; return this }
Run Code Online (Sandbox Code Playgroud)

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

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

02是函数声明的返回值.undefined应该在这里自我解释.

03使用在原始值的上下文中调用时five的返回值初始化变量.因为不是严格模式,所以此行与调用相同.dis5disfive = Object(5)

04奇数Number {[[PrimitiveValue]]: 5}返回值是包装原始值的对象的表示5

05five对象的wtf属性分配的字符串值'potato'

06 是赋值的返回值,应该是自解释的.

07five对象的wtf属性被检查

08five.wtf原先设置为'potato'返回'potato'这里

09five对象是由原始值乘以5.这与任何其他对象没有区别,并在ES5规范的第11.5节中进行了解释.特别值得注意的是如何将对象转换为数值,这将在几个部分中介绍.

9.3 ToNumber:

  1. 让primValue为ToPrimitive(输入参数,提示号).
  2. 返回ToNumber(primValue).

9.1 ToPrimitive:

返回Object的默认值.通过调用对象的[[DefaultValue]]内部方法,传递可选提示PreferredType来检索对象的默认值.对于8.12.8中的所有本机ECMAScript对象,此规范定义了[[DefaultValue]]内部方法的行为.

8.12.8 [[DefaultValue]]:

设valueOf是使用参数"valueOf"调用对象O的[[Get]]内部方法的结果.

  1. 如果IsCallable(valueOf)为真,那么,

    1. 设val是调用valueOf的[[Call]]内部方法的结果,其中O为此值和空参数列表.
    2. 如果val是原始值,则返回val.

这是一种迂回的说法,即调用对象的valueOf函数,并在等式中使用该函数的返回值.如果要更改valueOf功能,可以更改操作结果:

> five.valueOf = function () { return 10 }
undefined
> five * 5
50
Run Code Online (Sandbox Code Playgroud)

10fives valueOf函数未更改时,它返回包装的原始值,5以便five * 5计算5 * 5结果25

11five对象的wtf属性被评估一次,尽管已经从当它被分配上保持不变05.

12 'potato'

13后缀递增操作上调用five,它得到的数值(5,我们讨论了如何更早),存储,以便它可以被返回的值,增加1的值(6),并将值five,并返回存储的值(5)

14 与之前一样,返回值是增加之前的值

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

16 Number.prototype没有wtf属性,所以undefined返回

17 five.wtf被赋值为'potato?'.分配在ES5规范的11.13.1中定义.基本上,返回的值是返回但不存储.

18 'potato?' 由赋值运算符返回

19再次five,它具有6访问的值,并且再次Number.prototype没有wtf属性

20 undefined 如上所述

21 five 被访问

22 6 按照说明返回 13


Cal*_*ton 18

这很简单.

function dis () { return this; }
Run Code Online (Sandbox Code Playgroud)

这将返回this上下文.所以,如果你这样做,call(5)你将数字作为对象传递.

call函数不提供参数,您给出的第一个参数是上下文this.通常如果你想要它在上下文中,你就{}这样给它dis.call({}),这意味着this在函数中是空的this.但是,如果你传递5它似乎它将被转换为一个对象.见.call

所以回报是 object

当你这样做时five * 5,JavaScript将对象five视为基本类型,因此等同于5 * 5.有趣的是,确实如此'5' * 5,它仍然是平等的25,所以JavaScript显然是在引擎盖下.此行没有对基础five类型进行任何更改

但是当你这样做++时会将对象转换为基本number类型,从而删除.wtf属性.因为您正在影响基础类型


Tec*_*niv 11

原始值不能具有属性.但是当您尝试访问原始值的属性时,它会透明地转换为临时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
Run Code Online (Sandbox Code Playgroud)


Max*_*axx 9

声明功能dis.函数返回其上下文

function dis() { return this }
undefined
Run Code Online (Sandbox Code Playgroud)

dis用上下文调用5.当在严格模式(MDN)中作为上下文传递时,原始值被加框.所以five现在是对象(盒装数字).

five = dis.call(5)
Number {[[PrimitiveValue]]: 5}
Run Code Online (Sandbox Code Playgroud)

wtffive变量上声明属性

five.wtf = 'potato'
"potato"
Run Code Online (Sandbox Code Playgroud)

的价值 five.wtf

five.wtf
"potato"
Run Code Online (Sandbox Code Playgroud)

five是盒装的5,所以它的数量和对象是同时的(5*5 = 25).它没有变化five.

five * 5
25
Run Code Online (Sandbox Code Playgroud)

的价值 five.wtf

five.wtf
"potato"
Run Code Online (Sandbox Code Playgroud)

five在这里取消装箱.five现在只是原始的number.它打印5,然后添加1five.

five++
5
Run Code Online (Sandbox Code Playgroud)

five6现在是原始数字,它没有属性.

five.wtf
undefined
Run Code Online (Sandbox Code Playgroud)

原语不能有属性,你不能设置它

five.wtf = 'potato?'
"potato?"
Run Code Online (Sandbox Code Playgroud)

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

five.wtf
undefined
Run Code Online (Sandbox Code Playgroud)

five6因为上面的后增量

five
6
Run Code Online (Sandbox Code Playgroud)


Pat*_*arr 8

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

1.

    function dis() { return this }
Run Code Online (Sandbox Code Playgroud)

创建函数dis(),但因为它没有设置为var没有返回的值所以undefined即使dis()已定义,也是输出.在旁注中,this由于未执行该功能,因此未返回.

2.

    five = dis.call(5)
Run Code Online (Sandbox Code Playgroud)

这将返回javascript的Number对象,因为您只需将函数dis()this值设置为基元5.

3.

   five.wtf = 'potato'
Run Code Online (Sandbox Code Playgroud)

第一个返回"potato",因为你设置的属性wtffive'potato'.Javascript返回您设置的变量的值,可以轻松链接多个变量并将它们设置为相同的值,如下所示:a = b = c = 2.

4.

    five * 5
Run Code Online (Sandbox Code Playgroud)

这将返回25因为你只是乘以原始号码5five.值fiveNumber对象的值确定.

5.

    five.wtf
Run Code Online (Sandbox Code Playgroud)

之前我跳过了这一行,因为我会在这里重复一遍.它只返回wtf您在上面设置的属性的值.

6.

    five++
Run Code Online (Sandbox Code Playgroud)

正如@Callum所说,++将类型转换为number来自对象的相同值Number {[[PrimitiveValue]]: 5}}.

现在因为five是a number,你不能再设置它的属性,直到你做这样的事情:

    five = dis.call(five)
    five.wtf = "potato?"
Run Code Online (Sandbox Code Playgroud)

要么

    five = { value: 6, wtf: "potato?" }
Run Code Online (Sandbox Code Playgroud)

另请注意,第二种方式与使用第一种方法的行为不同,因为它定义的是通用对象,而不是Number之前创建的对象.

我希望这会有所帮助,javascript喜欢假设,所以当从Number对象转换为原语时会让人感到困惑number.你可以通过使用typeof关键字检查什么类型的东西,在初始化它之后写入五个类型返回'object',然后five++返回'number'.

@deceze非常好地描述了Number对象和原始数字之间的区别.


Tra*_*s J 6

JavaScript范围由执行上下文组成.每个执行上下文都有一个词法环境(外部/全局范围值),一个变量环境(本地范围的值)和一个这个绑定.

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

Function.prototype.call()(来自MDN)

句法
fun.call(thisArg[, arg1[, arg2[, ...]]])

thisArg
为乐趣调用提供的值.请注意,这可能不是方法看到的实际值:如果方法是非严格模式代码中的函数,则null和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"));
Run Code Online (Sandbox Code Playgroud)
<img src="http://i.stack.imgur.com/MUyRV.png" />
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述