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
时提供的 src
、action
和 arg
参数以及 func2
.
调用中提供的 v1
、v2
和 v3
参数
在第二个示例中,由于您删除了 func2
的参数,因此这些标识符在其创建的 onclick
函数范围内的唯一原因是全局变量;如果不是全局变量,您将得到 ReferenceError
s,因为这些标识符将无法解析。所以这是被使用的全局变量。
如果您在第二个示例中将参数传递给具有相同名称的 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
中捕获当时的全局变量,但我们最终只是复制了 src
、action
和 action
中已有的内容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')">
我正在学习闭包的工作原理并开始尝试一些代码。根据我的理解,闭包是某种内存,它在创建闭包的状态下保存函数的环境,并且即使在父函数结束时仍然存在(当返回或绑定到事件时)。
所以我尝试了下面的代码,它按预期工作。将相应文本框从 "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
时提供的 src
、action
和 arg
参数以及 func2
.
v1
、v2
和 v3
参数
在第二个示例中,由于您删除了 func2
的参数,因此这些标识符在其创建的 onclick
函数范围内的唯一原因是全局变量;如果不是全局变量,您将得到 ReferenceError
s,因为这些标识符将无法解析。所以这是被使用的全局变量。
如果您在第二个示例中将参数传递给具有相同名称的 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
中捕获当时的全局变量,但我们最终只是复制了 src
、action
和 action
中已有的内容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')">