此模式是否会导致闭包中出现循环引用

Does this pattern causes a circular reference in a closure?

本文关键字:循环 引用 闭包 模式 是否      更新时间:2023-09-26

我是javascript新手。我最近正在学习javascript及其出色的属性闭包。

但我对下面的代码片段感到困惑。

function outerFn() {
  var outerVar = {};
  function innerFn() {
    alert('haha');
  }
  outerVar.pro = innerFn;
  return innerFn;
}

我认为它不是innerFn和outerVar之间的循环引用,因为只有outerVar-pro指向innerFn。

但一些书指出,它仍然是一个循环参考。

有人能解释一下它是否是循环引用吗?提前谢谢。

可能在JavaScript引擎中导致循环引用(因为innerFn的父词法环境包括outerVal,其中包括innerFn),但它不会导致JavaScript代码可以观察到的循环引用。

outerFn运行时,会定义函数innerFn。在JavaScript中,新定义的函数可以访问范围中当前可访问的所有变量,因此innerFn内部的代码可以访问outerVar:

function outerFn() {
  var outerVar = {};
  function innerFn() {
    alert(outerVar);    // totally fine
  }
  return innerFn;
}

在ECMAScript术语中,这是因为每个函数都有一个词法环境,用于解析称为[[Scope]]的变量标识符。新定义的函数的[[Scope]]内部属性设置为其父函数的词法环境。因此,这里,innerFn[[Scope]]outerFn的词汇环境,其中包含对outerFn的引用。

在ECMAScript术语中,循环引用路径为:

  • innerFn
  • innerFn[[Scope]](一种词汇环境)
  • innerFn[[Scope]]的环境记录
  • innerFn[[Scope]]的环境记录中的outerVar绑定
  • innerFn[[Scope]]的环境记录中的outerVar绑定关联的变量
  • 该变量的属性值为innerFn

但是,由于无法从JavaScript代码中访问函数的[[Scope]]内部属性,因此无法从代码中观察到循环引用。

奖金信息

请注意,一个聪明的实现实际上不会在代码中存储这个循环引用,因为它可以看到outerVar从未在outerFn的任何子函数中使用过。当outerFn结束时,可以安全地完全忘记对outerVar的绑定。值得注意的是,eval不可能进行此优化,因为无法识别innerFn是否会使用outerVar:

function outerFn() {
  var outerVar = {};
  function innerFn(codeStr) {
    alert(eval(codeStr));    // will `codeStr` ever be "outerVar"?
  }
  return innerFn;
}