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

2020/10/10 18:41 · javascript ·  · 0评论

最近的一条推文包含此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)Number有某种的[[PrimitiveValue]]属性,但结果five++five * 5似乎只是普通号525(不Number为s)
  • 为什么five.wtf属性在five++增量后消失
  • 为什么five.wtf即使分配显然设置了该值five++属性在增量之后甚至都无法five.wtf = 'potato?'设置。

OP在这里。看到堆栈溢出很有趣:)

在逐步进行该行为之前,重要的是要澄清一些事情:

  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
    
  3. 每个表达式都有一个返回值。REPL读取并执行表达式时,它就是显示的内容。返回值通常并不表示您的想法,而是暗示不正确的事情。

好的,我们开始。

JavaScript代码的原始图像

誓言。

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

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

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

现在,将属性设置five.wtf'potato',并以5作为对象,确保它接受简单分配

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

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

转。

> five++
5
> five.wtf
undefined

现在我们检查five++后缀递增的技巧是整个表达式将根据原始值求值,然后递增该值。看起来five仍然是5,但实际上表达式的计算结果为5,然后设置five6

不仅five将其设置为6,而且还强制将其重新设置为Number值,并且所有属性均丢失。由于原语无法保存属性,five.wtf因此未定义。

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

我再次尝试将属性重新分配wtffive返回值表示它坚持,但实际上不是,因为five它是Number值,而不是Number对象。该表达式的计算结果为'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”关键字如何工作?我们只说to的第一个参数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"

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类?

我没有为接下来要查找的内容做准备。

事实证明,JavaScript一直在不告诉您的情况下,将您的数字更改为对象,而您的对象更改为您的鼻子下面的数字。

JavaScript希望没有人能追上来,但是人们一直在报告奇怪的意外行为,现在感谢您和您的问题,我有证据表明我需要对此事大肆宣扬。

这是我们到目前为止发现的。我不知道我是否该告诉您-您可能要关闭JavaScript。

> function dis() { return this }
undefined

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

没有错误消息,只是控制台输出中的单词“ undefined”,恰恰是您所期望的。毕竟,这是一个函数声明-不应返回任何内容。

但这仅仅是开始。接下来发生了什么,没人能预料到。

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

是的,我知道,您期望得到一个5,但是那不是您得到的,是-您得到了其他东西-有所不同。

这样的事情我也经历过。

我不知道该怎么做。它使我发疯。我无法入睡,无法进食,试图将其喝掉,但是没有多少山露会让我忘记。只是没有任何意义!

那是我发现真正发生的事情的时候-那是强制,它就在我眼前发生,但是我实在是太盲目看不到了。

Mozilla试图通过将其埋没在没人知道的地方-他们的文档来掩埋它

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

“ ...,原始值将转换为对象。”

就在这里,可以用Open Sans字体清楚地说明。这就是call()功能-我怎么会这么傻?

我的电话号码已不再是电话号码。当我将它传递进去的那一刻call(),它变成了别的东西。它变成了...一个对象。

起初我简直不敢相信。这怎么可能是真的?但是我不能忽略围绕我的证据。只要看一下就在这里:

> five.wtf = 'potato'
"potato"

> five.wtf
"potato"

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

我们应该知道看到控制台输出的那一刻-这不是我们认为的数字。这是一个冒名顶替者-一个物体冒充我们甜蜜的无辜数字。

这是... new Number(5)

当然!这很合理。call()有一件工作要做,他必须调用一个函数,然后要做他需要填充的事情this,他知道自己不能用数字来做到这一点-他需要一个对象,并且他愿意做任何事情来获得它,甚至如果那意味着强迫我们的电话号码。call()看到数字时5,他看到了机会。

这是一个完美的计划:等到没人看时,再用我们的号码换一个看起来像它的物体。我们得到一个数字,该函数被调用,没有人会更明智。

这确实是一个完美的计划,但是像所有计划一样,甚至是完美的计划,在其中也有一个漏洞,我们即将陷入其中。

瞧,call()不明白的是,他不是镇上唯一可以强迫数字的人。毕竟这是JavaScript-威逼无处不在。

call() 拿走了我的电话号码,直到我将面具从他的小骗子身上拉下并将他暴露给整个Stack Overflow社区,我才停下来。

但是如何?我需要一个计划。当然,它看起来像一个数字,但我知道不是,必须要有一种方法来证明这一点。而已!看起来像一个数字,但是它可以像一个数字一样起作用吗?

我告诉five我需要他变得大5倍-他没有问为什么,我也没有解释。然后,我做了任何优秀的程序员都会做的事情:我成倍增加。当然,他没有办法伪造他的出路。

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

该死的!不仅five乘法wtf不错该死的家伙和他的土豆。

到底是怎么回事?我整个事情都错了吗?five真的多少?不,我必须丢失一些东西,我知道它,我必须忘记一些东西,这些东西如此简单和基本,以至于我完全忽略了它。

这看起来不太好,我已经写了好几个小时的答案,但我离提出观点还差得远。我无法坚持下去,最终人们会停止阅读,我不得不思考一些事情,并且必须快速思考。

等一下!five不是25,结果是25,25是一个完全不同的数字。当然,我怎么会忘记?数字是不可变的。当乘以5 * 5任何东西时,您将只创建一个新数字25

那一定是这里发生的事情。我以某种方式乘时five * 5five必须被强制转换为数字,并且该数字必须是用于乘法的数字。是乘积的结果被打印到控制台上,而不是其five本身的值five永远不会被分配任何东西-因此,它当然不会改变。

因此,我该如何five为自己分配操作结果。我知道了。之前five甚至有一个思考的机会,我大喊:“++”。

> five++
5

啊哈!我有他!大家都知道5 + 16,这是我需要公开的证据five是不是一个数字!那是骗子!一个糟糕的冒名顶替者,不知道如何计算。我可以证明这一点。这是实数的行为:

> num = 5
5
> num++
5

等待?这是怎么回事 叹息,我陷入了破产的困境,five以至于我忘记了邮政接线员的工作方式。当我++在末尾使用时,five是说要返回当前值,然后递增five将操作发生之前的值打印到控制台。num实际上6,我可以证明这一点:

>num
6

是时候看看five真正的含义了

>five
6

...这正是应该的。five很好-但是我更好。如果five仍然是一个对象,那意味着它仍然会拥有该属性,wtf而我愿意打赌它没有的一切。

> five.wtf
undefined

啊哈!我是对的。我有他!five现在是一个数字-它不再是一个对象。我知道乘法技巧这次不会保存。five++是真的five = five + 1与乘法不同,运算++符为分配一个值five更具体地,分配给它的结果five + 1,其就像在乘法返回一个新的不可变的情况下

我知道我有他,只是为了确保他不会从他那儿弯腰。我又袖手旁观了。如果我是对的,并且five现在确实是一个数字,那么这将不起作用:

> five.wtf = 'potato?'
'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返回上下文对象的函数this代表什么变化取决于您是否使用严格模式。如果将该函数声明为:整个示例将得到不同的结果:

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

ES5规范的10.4.3节对此进行了详细说明

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

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

03该变量five使用dis在原始值的上下文中调用when的返回值初始化5由于dis不在严格模式下,因此此行与call相同five = Object(5)

04Number {[[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

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

8.12.8 [[DefaultValue]]

令valueOf为使用参数“ valueOf”调用对象O的[[Get]]内部方法的结果。

  1. 如果IsCallable(valueOf)为true,则

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

这是一种round回的说法,即调用对象的valueOf函数并将该函数的返回值用于方程式。如果要更改valueOf功能,则可以更改操作结果:

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

10由于fivesvalueOf函数未更改,因此它返回包装的原始值,5以便five * 5计算5 * 5得出25

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

12 'potato'

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

14 和以前一样,返回的值是它递增之前的值

15访问存储在变量wtf中的原始值(6属性fiveES5规范的第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

很简单

function dis () { return this; }

这将返回this上下文。因此,如果您这样做,则将call(5)数字作为对象传递。

call函数不提供参数,您提供的第一个参数是的上下文this通常,如果您希望在上下文中使用它,则可以{}这样指定dis.call({}),这意味着this该函数为空this但是,如果通过,5它似乎将被转换为对象。参见.call

所以回报是 object

当您这样做时five * 5,JavaScript会将对象five视为原始类型,因此等效于5 * 5有趣的是,do'5' * 5仍然等于25,因此JavaScript显然可以在后台进行转换。此行未对基础five类型进行任何更改

但是,当您执行++操作时,它将把对象转换为原始number类型,从而删除该.wtf属性。因为您正在影响基础类型

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

调用dis上下文5当在严格模式(MDN)中作为上下文传递原始值时,会将它们装箱所以five现在是对象(盒装数字)。

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

声明变量的wtf属性five

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,然后添加1five

five++
5

five6现在是原始编号,其中没有属性。

five.wtf
undefined

基本体不能具有属性,您不能设置此属性

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

您无法阅读,因为未设置

five.wtf
undefined

five6由于职位增加以上

five
6

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

1。

    function dis() { return this }

创建函数dis(),但由于未将其设置为a var,因此undefined即使dis()已定义,也没有返回值,因此输出也没有在旁注中,this由于未执行功能而未返回。

2。

    five = dis.call(5)

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

3。

   five.wtf = 'potato'

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

4。

    five * 5

这种回报25,因为你只是乘以原始号码5five的值fiveNumber对象的值确定

5,

    five.wtf

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

6。

    five++

就像@Callum所说的,++将把type转换成number来自object的相同值Number {[[PrimitiveValue]]: 5}}

现在因为fivenumber,您不能再为其设置属性,除非您执行以下操作:

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

要么

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

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

我希望这会有所帮助,javascript喜欢假设事物,因此在从Number对象更改为基本类型时可能会引起混淆number您可以检查是什么类型的东西,通过使用typeof关键字,写的typeof 5初始化后返回'object',并在此之后five++返回'number'

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

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

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

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

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

thisArg

为fun调用提供的this值。
请注意,这可能不是该方法看到的实际值:如果该方法是非严格模式代码中的函数,则null和undefined将被全局对象替换,
原始值将转换为objects(强调我的)

一旦很明显将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引擎都会创建适当类型的对象包装器(Numberfor 5Stringfor'str'Booleanfor true),并在该对象包装器上解析属性访问/方法调用。例如,这就是发生的情况true.toString()

对对象执行操作时,它们将转换为原始值(通过使用toStringvalueOf)以解决这些操作-例如,在执行操作时

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

string将持有的字符串连接mystr,并obj.toString()number将持有的加3obj.valueOf()

现在放在一起

five = dis.call(5)

dis.call(5)行为就像(5).dis()如果5确实有方法dis为了解决方法调用,创建了对象包装,并在其上解决了方法调用。此时,五个指向原始值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不再是一个对象。这些行中的每一行都会创建Number的新实例,以解决对.wtf属性的访问。实例是独立的,因此在一个实例上设置属性将在另一实例上不可见。该代码完全等效于以下代码:

(new Number(6)).wtf;
(new Number(6)).wtf = 'potato?';
(new Number(6)).wtf;
本文地址:http://javascript.askforanswer.com/shiyongnumberduixiangbaocunshuxingbingdizengshuzidezheduandaimazhongfashengleshenme.html
文章标签:
版权声明:本文为原创文章,版权归 javascript 所有,欢迎分享本文,转载请保留出处!

文件下载

老薛主机终身7折优惠码boke112

上一篇:
下一篇:

评论已关闭!