Javascript闭包和全局变量

时间:2015-08-19 09:10:44

标签: javascript closures

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

所以我尝试了下面的代码,它按预期工作。两个按钮,将相应的文本框从“检查”切换为“已保存”。

//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 - &gt; Textbox1 = check;点击Go2 - &gt; Textbox2 = check;但现在点击Go1 - &gt; 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')">

1 个答案:

答案 0 :(得分:1)

  

所以闭包中的变量v1,v2,v3似乎仍然使用闭包之外的全局值

是的,因为没有任何东西影响他们,所以他们最终会如何解决。

在您的第一个代码块中,onclick处理程序正在使用src调用中提供的actionargfunc1参数v1调用中提供的v2v3func2参数。

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

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

解释这条线的作用可能很有用:

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

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

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

&#13;
&#13;
// 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
&#13;
<!-- 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>
&#13;
&#13;
&#13;

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

此代码段演示了闭包的内容是对arg的引用,而不是对其值的副本的引用:

&#13;
&#13;
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
&#13;
<!-- 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>
&#13;
&#13;
&#13;

您可以在我的贫穷小博客上找到这篇文章有用:Closures are not complicated

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

&#13;
&#13;
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);
  };
}
&#13;
<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')">
&#13;
&#13;
&#13;