[removed] function test() { var els = document.getElementById("test").getElementsByTagName("div"); for (var i = 0; i < els> { var div = els[i]; div.onclick = function() { alert(div[removed]); return false; } } } test(); [removed] 无论我们点击哪个div,反馈的都是第4个div的内容。究其原因,在于每个div的点击事件都与test方法形成了闭包,且每个div的点击事件都共享同一个闭包作用域链。当事件被触发时,变量i所代表的下标已经指向第4个div。可以采用以下几种方式避免由于闭包引起的问题。 (1)使用this转换闭包的作用域链上下文,上例的闭包可以改写为: for (var i = 0; i < els> { var div = els[i]; div.onclick = function() { alert(this[removed]); return false; } } 当点击div的事件被触发时,查找的作用域已经是“this”所指定的上下文。尽管该事件仍然处于“test”闭包内,但由于不访问或不使用闭包的上下文环境,也就不存在由于闭包作用域内变量被引用所引发的问题。 (2)使点击div的事件与for循环形成闭包,而使得for循环内的变量div不被回收。如: //for循环内定义闭包方法 for (var i = 0; i < els> { var div = els[i]; a(div); function a(o) { o.onclick = function() { alert(o[removed]); } } } //for循环外定义闭包方法 for (var i = 0; i < els> { var div = els[i]; a(div); } function a(o) { o.onclick = function() { alert(o[removed]); } } //使用匿名方法,其原理与for循环内定义类似 for (var i = 0; i < els> { var div = els[i]; (function(o) { o.onclick = function() { alert(o[removed]); } })(div); } 通过中间方法a或者匿名方法,使for循环体与onclick事情产生闭包。 (3)控制变量的作用域,使点击div的事件所需变量与外层作用域无关。如: for (var i = 0; i < els> { (function() { var div = els[i]; div.onclick = function() { alert(div[removed]); } })(); } 内部函数自身也可能有内部函数。每次作用域链嵌套,都会增加由创建内部函数对象的执行环境所引发的新活动对象。ECMA262规范要求作用域链是临时性的,但对作用域链的长度却没有加以限制。闭包的潜规则即Function与内部定义的Function之间的相互作用域链上下文环境的关系。
同事在初始化redis配置的时候,给Dial函数赋值时用了闭包,导致程序上线后,数据怎么都加载不到redis中去,排查了半个多小时,总算找到了罪魁祸首。虽然自己之前对闭包也算了解,但是看到他的那段代码的时候,乍一看竟也没发现出问题来,所以决定写篇文章加深印象,避免自己以后也犯类似的问题。
先上代码:
func InitRedis() error {
GRedis = make(map[string]*Cache)
for _, gConfig := range config.GConfigs.RedisList { //遍历配置文件,redisList存放了各个redis服务器的ip、端口号以及密码等信息。
c := new(Cache)
c.pool = &redis.Pool{
MaxIdle: gConfig.Conf.MaxConn,
MaxActive: gConfig.Conf.MaxConn,
IdleTimeout: 240 * time.Second,
Dial: func() (redis.Conn, error) { //连接实例的Dial函数,这里用了闭包,其中gConfig为自由变量
c, err := redis.Dial("tcp", gConfig.Conf.IP+":"+gConfig.Conf.Port, redis.DialConnectTimeout(60*time.Second), redis.DialReadTimeout(60*time.Second), redis.DialWriteTimeout(60*time.Second))
if err != nil {
return nil, err
}
if gConfig.Conf.Password != "" {
if _, err := c.Do("AUTH", gConfig.Conf.Password); err != nil {
c.Close()
return nil, err
}
}
return c, err
},
}
c.prefix = gConfig.Conf.Prefix
GRedis[gConfig.Name] = c
}
return nil
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
上述代码已对关键部分(涉及公司机密)进行了删除和处理。
乍一看是不是很难发现问题到底出在哪,我们当时碰到的问题是,数据没有从db加载到redis服务器1中,经过排查,发现db的数据全加载到了redis服务器3中了。恰巧redis服务器3是配置文件中的最后一个,也就是for循环中遍历的最后一个。进而我们发现Dial函数对应的闭包函数里引用了gConfig这个自由变量。
这会导致什么问题呢?我们先来温习一下闭包的特性:有自由变量的匿名函数是闭包,闭包的特性,就是内层函数引用了外层函数中的变量,注意是引用哦。
所以,这会导致redis连接池中每个连接去做Dial的时候,拿到的都是for循环中最后一个gConfig,即连接的都是redis服务器3,去redis服务器1里面拿数据当然就什么都拿不到了。
一个是外部可以读取内部函数的变量,二是变量会始终在内存中,不会被回收机制回收,三是避免全局变量被污染,方便调用上下文的局部变量,加强封装性。
大量使用闭包会导致内存泄露,所以不用的变量应该手动设置为null。
在初始化redis配置的时候,给Dial函数赋值时用了闭包,导致程序上线后,数据怎么都加载不到redis中去,排查了半个多小时,总算找到了罪魁祸首。虽然自己之前对闭包也算了解,但是看到他的那段代码的时候,乍一看竟也没发现出问题来,所以决定写篇文章加深印象,避免自己以后也犯类似的问题。
同事在初始化redis配置的时候,给Dial函数赋值时用了闭包,导致程序上线后,数据怎么都加载不到redis中去,排查了半个多小时,总算找到了罪魁祸首。虽然自己之前对闭包也算了解,但是看到他的那段代码的时候,乍一看竟也没发现出问题来,所以决定写篇文章加深印象,避免自己以后也犯类似的问题。
先上代码:
func InitRedis() error {
GRedis = make(map[string]*Cache)
for _, gConfig := range config.GConfigs.RedisList { //遍历配置文件,redisList存放了各个redis服务器的ip、端口号以及密码等信息。
c := new(Cache)
c.pool = &redis.Pool{
MaxIdle: gConfig.Conf.MaxConn,
MaxActive: gConfig.Conf.MaxConn,
IdleTimeout: 240 * time.Second,
Dial: func() (redis.Conn, error) { //连接实例的Dial函数,这里用了闭包,其中gConfig为自由变量
c, err := redis.Dial("tcp", gConfig.Conf.IP+":"+gConfig.Conf.Port, redis.DialConnectTimeout(60*time.Second), redis.DialReadTimeout(60*time.Second), redis.DialWriteTimeout(60*time.Second))
if err != nil {
return nil, err
}
if gConfig.Conf.Password != "" {
if _, err := c.Do("AUTH", gConfig.Conf.Password); err != nil {
c.Close()
return nil, err
}
}
return c, err
},
}
c.prefix = gConfig.Conf.Prefix
GRedis[gConfig.Name] = c
}
return nil
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
上述代码已对关键部分(涉及公司机密)进行了删除和处理。
乍一看是不是很难发现问题到底出在哪,我们当时碰到的问题是,数据没有从db加载到redis服务器1中,经过排查,发现db的数据全加载到了redis服务器3中了。恰巧redis服务器3是配置文件中的最后一个,也就是for循环中遍历的最后一个。进而我们发现Dial函数对应的闭包函数里引用了gConfig这个自由变量。
这会导致什么问题呢?我们先来温习一下闭包的特性:有自由变量的匿名函数是闭包,闭包的特性,就是内层函数引用了外层函数中的变量,注意是引用哦。
所以,这会导致redis连接池中每个连接去做Dial的时候,拿到的都是for循环中最后一个gConfig,即连接的都是redis服务器3,去redis服务器1里面拿数据当然就什么都拿不到了。
同事在初始化redis配置的时候,给Dial函数赋值时用了闭包,导致程序上线后,数据怎么都加载不到redis中去,排查了半个多小时,总算找到了罪魁祸首。虽然自己之前对闭包也算了解,但是看到他的那段代码的时候,乍一看竟也没发现出问题来,所以决定写篇文章加深印象,避免自己以后也犯类似的问题。
闭包(closure)是 JavasSript 的一大难点,也是它的特色。很多高级应用都要依靠闭包来实现。
变量作用域
要理解闭包,首先要理解 JavasSript 的特殊的变量作用域。
变量的作用域无非就两种:全局变量和局部变量。
JavasSript 语言的特别之处就在于:函数内部可以直接读取全局变量,但是在函数外部无法读取函数内部的局部变量。
注意点:在函数内部声明变量的时候,一定要使用 var 命令。如果不用的话,你实际上声明的是一个全局变量!
闭包的功能强大,但如果没有正确理解闭包的概念,其结果往往出乎人的意料。例如,下面是一个较常见的问题:
第一个
第二个
第三个
第四个
[removed]
function test()
{
var els = document.getElementById("test").getElementsByTagName("div");
for (var i = 0; i < els>
{
var div = els[i];
div.onclick = function()
{
alert(div[removed]);
return false;
}
}
}
test();
[removed]
无论我们点击哪个div,反馈的都是第4个div的内容。究其原因,在于每个div的点击事件都与test方法形成了闭包,且每个div的点击事件都共享同一个闭包作用域链。当事件被触发时,变量i所代表的下标已经指向第4个div。可以采用以下几种方式避免由于闭包引起的问题。
(1)使用this转换闭包的作用域链上下文,上例的闭包可以改写为:
for (var i = 0; i < els>
{
var div = els[i];
div.onclick = function()
{
alert(this[removed]);
return false;
}
}
当点击div的事件被触发时,查找的作用域已经是“this”所指定的上下文。尽管该事件仍然处于“test”闭包内,但由于不访问或不使用闭包的上下文环境,也就不存在由于闭包作用域内变量被引用所引发的问题。
(2)使点击div的事件与for循环形成闭包,而使得for循环内的变量div不被回收。如:
//for循环内定义闭包方法
for (var i = 0; i < els>
{
var div = els[i];
a(div);
function a(o)
{
o.onclick = function()
{
alert(o[removed]);
}
}
}
//for循环外定义闭包方法
for (var i = 0; i < els>
{
var div = els[i];
a(div);
}
function a(o)
{
o.onclick = function()
{
alert(o[removed]);
}
}
//使用匿名方法,其原理与for循环内定义类似
for (var i = 0; i < els>
{
var div = els[i];
(function(o)
{
o.onclick = function()
{
alert(o[removed]);
}
})(div);
}
通过中间方法a或者匿名方法,使for循环体与onclick事情产生闭包。
(3)控制变量的作用域,使点击div的事件所需变量与外层作用域无关。如:
for (var i = 0; i < els>
{
(function()
{
var div = els[i];
div.onclick = function()
{
alert(div[removed]);
}
})();
}
内部函数自身也可能有内部函数。每次作用域链嵌套,都会增加由创建内部函数对象的执行环境所引发的新活动对象。ECMA262规范要求作用域链是临时性的,但对作用域链的长度却没有加以限制。闭包的潜规则即Function与内部定义的Function之间的相互作用域链上下文环境的关系。
闭包的特性:
有自由变量的匿名函数是闭包,闭包的特性,就是内层函数引用了外层函数中的变量,注意是引用哦。
所以,这会导致redis连接池中每个连接去做Dial的时候,拿到的都是for循环中最后一个gConfig,即连接的都是
redis服务器3,去redis服务器1里面拿数据当然就什么都拿不到了。
一周热门 更多>