允许 `p.foo = o.foo` 到 return 函数 `foo` 的引用的 javascript mechanism/rules 是什么?

what is the javascript mechanism/rules that allow `p.foo = o.foo` to return a reference to the function `foo`?

我目前正在学习javascript "you dont know js" 系列丛书。

在 "this & object prototype" 部分,在讨论 "indirect references to functions" 时,作者指出

function foo() {
  console.log( this.a );
}

var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4 };

o.foo(); // 3
(p.foo = o.foo)(); // 2

The result value of the assignment expression p.foo = o.foo is a reference to just the underlying function object. As such, the effective call-site is just foo(), not p.foo() or o.foo() as you might expect. Per the rules above, the default binding rule applies.

显然,(p.foo = o.foo) return 是对函数 foo 的引用。但是允许 (p.foo = o.foo) return 引用函数 foo 的 mechanism/rules 是什么?换句话说,为什么要简单地赋值 return 对 foo 函数的引用?

当我想理解这样的东西时,我发现逐步分解它很有帮助。

  1. o.foo 查看 o 对象并找到一个名为 foo 的 属性。它 returns 是 属性 的引用,无论它是什么。在这种情况下,o.foo 属性 是对函数 foo.
  2. 的引用
  3. p.foo = o.foo 从上面获取结果(对函数 foo 的引用),在 p 对象中创建一个 属性 也命名为 foo.所以现在 p.foo 也是对 foo 函数的引用,与 o.foo.
  4. 完全一样
  5. 该表达式包含在括号中,所以现在您拥有 = 符号或 p.foo 左侧的任何内容,这(作为提醒)仍然是对foo 函数。
  6. 现在我们在最后找到 ()。这会调用我们此时手头上的任何函数。那就是 foo 函数。请特别注意,我们 不是 调用 p.foo()。那将是对 p.foo 引用的函数的方法调用,因此在该函数内部,this 将设置为 p。但我们没有这样做。我们只是调用 ( p.foo = o.foo ) 返回的任何函数。和以前一样,这是相同的 foo 函数,但我们现在已经失去了它可能与 o 对象或 p 对象的任何联系。
  7. 因此,当我们最后进行该调用时,我们只是调用了 foo 函数,而没有将 this 设置为任何特定对象。因此,当我们进行调用时,this 设置为 undefined
  8. 但是我们不是 运行 处于 strict 模式,所以 JavaScript "helpfully" 不想给我们一个未定义的 this,所以它将 this 设置为浏览器中的 window 对象或 Node 中的 global 对象。
  9. 之前我们做过 var a = 2;。所以 windowglobal 对象实际上现在有一个名为 a 的 属性,并且 属性 的值是 2.
  10. 所以现在当我们执行 console.log(this.a) 时,我们从 windowglobal 对象中获取 a 属性。该值为 2.

如果所有这些代码都不是 运行 在全局级别,而是在函数内部怎么办?那会发生什么?

function test() {
  function foo() {
    console.log( this.a );
  }
  
  var a = 2;
  var o = { a: 3, foo: foo };
  var p = { a: 4 };

  o.foo(); // 3
  (p.foo = o.foo)(); // was 2, but now is undefined
}

test();

现在当我们在 foo 中调用 console.log( this.a ); 时,this 仍然引用 windowglobal 对象。但是当我们设置 var a = 2; 时,我们不再设置全局 属性。我们只是在创建一个局部变量。 window.aglobal.aundefined(除非之前有其他代码设置)。

严格模式避免了一些这种怪异现象。如果我们在代码的顶部放置一个 'use strict'; ,它将以严格模式编译。现在当我们在最后调用函数 foo 时(同样不是方法!),this 现在设置为 undefined 而不是windowglobal。因此,当我们尝试调用 console.log(this.a) 时,代码会失败,因为 thisundefined 相同,而 undefined 没有(也不能)有一个 a 属性.

让我们试试看:

'use strict';

function foo() {
  console.log( this.a );
}

var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4 };

o.foo(); // 3
(p.foo = o.foo)(); // was 2 in the original, but now throws an exception

底线,至少对于这个特定的例子:总是使用严格模式!是你的朋友。

赋值运算符 = 是 JavaScript 中的一个表达式,它产生 (returns) 赋值。因为它是一个表达式,所以它可以用在任何允许使用表达式的地方,例如括号内。

例如:

let test = (a = b = c = { name: 'test' })

上面的代码首先计算括号中的表达式并将变量 cba 指向测试对象(按此顺序),然​​后它会将 test 指向此表达式的生成值。执行该行后,abctest 将指向同一个对象。

同样,

(p.foo = o.foo)

会产生 o.foo 返回(技术上它会产生任何 o.foo 指向的东西,也就是函数 foo)。

至于

(p.foo = o.foo)()

通过在括号后添加额外的 (),我们告诉引擎我们想要调用表达式 (p.foo = o.foo) 最终生成的任何内容。因此我们最终调用函数 fooSimilar patterns is used in IIFEs.

一个有用的重写是将上面的行视为这样做:

let produced = (p.foo = o.foo)
produced()

Further reading about statements vs expressions.