Javascript 闭包和全局变量

Javascript closures and global variables

我正在学习闭包的工作原理并开始尝试一些代码。根据我的理解,闭包是某种内存,它在创建闭包的状态下保存函数的环境,并且即使在父函数结束时仍然存在(当返回或绑定到事件时)。

所以我尝试了下面的代码,它按预期工作。将相应文本框从 "check" 切换到 "saved".

的两个按钮

//var v1;
//var v2;
//var v3;

function func1(src, action, arg) {
  document.getElementById(arg).value = "check";
  document.getElementById(src.id).onclick = function() {
    func2(src, action, arg);
  };
  //v1 = src;
  //v2 = action;
  //v3 = arg;
}

function func2(v1, v2, v3) {
  document.getElementById(v3).value = "saved";
  document.getElementById(v1.id).onclick = function() {
    func1(v1, v2, v3);
  };
}
<input type=text id="t1">
<input type=button id="btn1" value="Go 1" onclick="func1(this, 'edit', 't1')">
<input type=text id="t2">
<input type=button id="btn2" value="Go 2" onclick="func1(this, 'edit', 't2')">

但现在混乱来了。当我使用全局变量在 func2() 中构造闭包时,开关会出现错误。请参阅下面的代码:

var v1;
var v2;
var v3;

function func1(src, action, arg) {
  document.getElementById(arg).value = "check";
  document.getElementById(src.id).onclick = function() {
    func2();
  };
  v1 = src;
  v2 = action;
  v3 = arg;
}

function func2() {
  document.getElementById(v3).value = "saved";
  document.getElementById(v1.id).onclick = function() {
    func1(v1, v2, v3);
  };
}
<input type=text id="t1">
<input type=button id="btn1" value="Go 1" onclick="func1(this,'edit','t1')">
<input type=text id="t2">
<input type=button id="btn2" value="Go 2" onclick="func1(this,'edit','t2')">

点击 Go1 -> Textbox1 = 勾选;点击 Go2 -> Textbox2 = 勾选;但现在点击 Go1 -> Textbox2(而不是 1)= 已保存。

所以看起来闭包中的变量v1,v2,v3还是使用闭包外的全局值。有人可以解释为什么以及如何使用全局变量吗?


感谢 T.J.Crowder 我更新了我的代码以在闭包中使用私有变量。但不幸的是它仍然不起作用。与第二个代码块的行为相同。

var v1;
var v2;
var v3;

function func1(src, action, arg) {
  document.getElementById(arg).value = "check";
  document.getElementById(src.id).onclick = function() {
    func2();
  };
  v1 = src;
  v2 = action;
  v3 = arg;
}

function func2() {
  var private1 = v1;
  var private2 = v2;
  var private3 = v3;

  document.getElementById(private3).value = "saved";
  document.getElementById(private1.id).onclick = function() {
    func1(private1, private2, private3);
  };
}
<input type=text id="t1">
<input type=button id="btn1" value="Go 1" onclick="func1(this,'edit','t1')">
<input type=text id="t2">
<input type=button id="btn2" value="Go 2" onclick="func1(this,'edit','t2')">

So it seems that the variables v1, v2, v3 in the closure still uses the global values outside the closure

是的,因为没有什么能遮蔽他们,所以他们最终就是这样解决的。

在您的第一个代码块中,onclick 处理程序正在使用调用 func1 时提供的 srcactionarg 参数以及 func2.

调用中提供的 v1v2v3 参数

在第二个示例中,由于您删除了 func2 的参数,因此这些标识符在其创建的 onclick 函数范围内的唯一原因是全局变量;如果不是全局变量,您将得到 ReferenceErrors,因为这些标识符将无法解析。所以这是被使用的全局变量。

如果您在第二个示例中将参数传递给具有相同名称的 func2(这不是一个好主意,但只是为了解释),那么 onclick 使用的标识符它创建的闭包将解决这些参数而不是全局变量。

解释这一行的作用可能会有用:

document.getElementById(v1.id).onclick = function () { func1(v1,v2,v3); };

该行创建了一个函数,它是创建它的上下文的闭包,对 func2 的调用(并且该上下文指的是创建它的上下文,依此类推到全球范围内)。 onclick 闭包没有创建时范围内变量的 副本 ,它有一个 持久引用 给他们。因此,当函数获得 运行 时,使用的是变量 在那个时刻 的值。在您的第一个示例中,这些值永远不会改变,因为它们是在创建闭包的调用期间传递给 func2 的参数的值,并且没有任何改变。但是,在您的第二个示例中,这些值不同,因为您使用的是全局变量,而不是传递给 func2.

的参数

因为闭包有一个对其创建上下文的引用,每次调用func2 的上下文都由在该调用中创建的闭包保留。因此,如果保留由多个调用创建的闭包,则会为对 func2 的调用保留多个上下文,因此会保留参数的多个副本(每个副本都由为该上下文创建的闭包使用)。一个更简单的例子可能会有所帮助:

// A global variable
var global = "g";

// A function that creates and returns a closure
function foo(arg) {
    return function() {
        snippet.log("global = " + global +) ", arg = " + arg);
    };
}

// Create a closure over arg = 42
var f1 = foo(42);
f1(); // global = g, arg = 42

// Change global
global = "g+";
f1(); // global = g+, arg = 42

// Create a second closure over a second arg, 67
var f2 = foo(67);
f2(); // global = g+, arg = 67

// Change global again
global = "g++";
f1(); // global = g++, arg = 42
f2(); // global = g++, arg = 67
<!-- Script provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>

请注意每个闭包如何拥有自己的 arg 副本,但它们都共享 global.

此代码段演示了闭包所具有的是对 arg 的引用,而不是其值的 copy

function foo(arg) {
  return {
    showArg: function() {
      console.log("arg = " + arg);
    },
    incrementArg: function() {
      ++arg;
    }
  };
}
var o1 = foo(42);
o1.showArg(); // arg = 42
o1.incrementArg();
o1.showArg(); // arg = 43
<!-- Script provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>

你可能会发现我贫血的小博客上的这篇文章很有用:Closures are not complicated


这是一个片段,演示了在闭包调用 func2 中捕获当时的全局变量,但我们最终只是复制了 srcactionaction 中已有的内容arg,所以没有意义。

var v1;
var v2;
var v3;

function func1(src, action, arg) {
  var p1 = src, p2 = action, p3 = arg; // Just duplicates what we already have
  document.getElementById(arg).value = "check";
  document.getElementById(src.id).onclick = function() {
    func2(p1, p2, p3); // We could just use src, action, and arg here like your first example
  };
  v1 = src;
  v2 = action;
  v3 = arg;
}

function func2(v1, v2, v3) {
  document.getElementById(v3).value = "saved";
  document.getElementById(v1.id).onclick = function() {
    func1(v1, v2, v3);
  };
}
<input type=text id="t1">
<input type=button id="btn1" value="Go 1" onclick="func1(this,'edit','t1')">
<input type=text id="t2">
<input type=button id="btn2" value="Go 2" onclick="func1(this,'edit','t2')">