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


当前回答

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

其他回答

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所述

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

声明函数dis。函数返回其上下文

function dis() { return this }
undefined

用context 5调用dis。原始值在严格模式(MDN)中作为上下文传递时被装箱。所以5现在是object(盒装数字)

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

在五个变量上声明wtf属性

five.wtf = 'potato'
"potato"

5 .wtf的值

five.wtf
"potato"

5被框5,所以它同时是数字和对象(5 * 5 = 25)。它不会改变5。

five * 5
25

5 .wtf的值

five.wtf
"potato"

在这里打开5。5现在只是一个原始数。它输出5,然后5加1。

five++
5

5现在是原数6,它没有任何属性。

five.wtf
undefined

基本类型不能有属性,你不能设置这个

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

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

five.wtf
undefined

5是6,因为上面的增量

five
6

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

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;

这很简单。

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属性。因为您正在影响底层类型