0
回答
揭秘javascript:欣赏闭包之美
利用AWS快速构建适用于生产的无服务器应用程序,免费试用12个月>>>   

在javascript中编写函数时,我们应该明白函数不仅仅是一个代码块: 函数是javascript中的标准数据类型,实际上就是对象;你可以传递和复制他们。
让我们先看看下面这个简短的函数示例,他创建并返回了另一个函数。这个返回的函数接收一个字符串类型的参数,并将这个字符串重复若干次后返回。

function makeRepeater(times){
    return function(text){
        var message = '';
        for (var i=0; i < times; i++) {
            message += text + ' ';
        }
        return message;
    };
}

下面是调用上述函数的代码:

var threeTimes = makeRepeater(3);
var fourTimes = makeRepeater(4);
alert( threeTimes('hi') );
// => 'hi hi hi '
alert( fourTimes('hi') );
// => 'hi hi hi hi '

没什么特别之处,是吧?请你再仔细的看一看,makeRepeater返回的函数包含了一个对times变量的引用,而times变量却是 makeRepeater函数的局部变量。当我们调用threeTimes或者fourTimes的时候,makeRepeater的调用已经返 回,times已经离开它了的作用域,是不是这样?

为局部作用域续命

你也许会说threeTimes里面的times变量不是makeRepeater中times变量的引用,而仅仅是复制了它的值。好吧,让我们来证明你是错的。将此前的代码稍作修改:

var times;
 
function makeRepeater(){
    return function(text){
        var message = '';
        for (var i=0; i < times; i++) {
            message += text + ' ';
        }
        return message;
    };
}
 
times = 3;
var threeTimes = makeRepeater();
times = 4;
var fourTimes = makeRepeater();
 
alert( threeTimes('hi') );
// => 'hi hi hi hi '  ---> What?!?!
alert( fourTimes('hi') );
// => 'hi hi hi hi '

如果你还没明白过来,那让我们一起来分析一下。返回的函数确实维持了在它被调用时需要用到的外部变量的引用。在我们第一个例子中,当返回函数被创建 时,他就维持了对局部变量times的引用。如果我们在makeRepeater函数中创建了其他局部变量,那么他们在返回的函数中也是可以访问的。换句 话说,当返回函数维持了一个对父(外部)函数任意成员的引用时,makeRepeater的作用域都被返回函数所拥有,即作为返回函数的父作用域。当这种 情况发生的时候,我们便创建了一个闭包。

闭包有时候很狡猾

理解闭包机制的重要性在于可以帮助我们很好的避免在代码中出现难以发现的bug。看看下面这个代码片段,它源自于我修复的一个真实的bug。

<input type="button" value="Button 1" id="btn1">
<input type="button" value="Button 2" id="btn2">
<input type="button" value="Button 3" id="btn3">  
 
<script type="text/javascript">
    function createEventHandlers(){
        var btn;
        for(var i=1; i <= 3; i++){
            btn = document.getElementById('btn' + i);
            btn.onclick = function(){
                alert('Clicked button #' + i);
            }
        }
    }
    createEventHandlers();
</script>

如果你将这些代码放到一个页面中并点击那三个按钮,你会发现他们弹出的信息都是“Clicked button #4”。借助对闭包机制的理解,我们可以马上明白这个bug是由于内部点击事件的处理函数引用外部函数的局部变量i导致的。我可以这样修复它:

function createEventHandlers(){
    var btn;
    for(var i=1; i <= 3; i++){
        btn = document.getElementById('btn' + i);
        btn.onclick = createOneHandler(i);
    }
}  

function createOneHandler(number){
    return function() {
        alert('Clicked button #' + number);
    }
}

上面的代码可以正常工作,因为我们没有在for循环中创建函数,因此没有在这个局部作用域中产生闭包。我们虽然在createOneHandler 创建了一系列的闭包,但是他们并没有指向同一个父作用域。他们各自包含一个单独的作用域,这些作用域在每次调用createOneHandler时创建。

闭包的思想

闭包当然不是javascript独有的,他是函数式语言非常重要的特性之一。即使是在c#中,当我们使用lambda表达式的时候,就会产生闭包————只是大多数时候,我们没有意识到。

在代码中正确使用闭包的关键,是要注意外部函数作用域中被内部函数使用的那些变量的值。绝大多数时候,它们会如开发者预期的那样工作,但是当它们不 能正常工作时,停下来检查下是否有超过一个的闭包在共享相同的局部作用域,或者这些局部变量的值是否在创建内部函数时和调用内部函的时都被修改。

本文翻译自:JavaScript, time to grok closures

文章转载自:铁骑世界(http://cooleep.com/)

举报
鉴客
发帖于6年前 0回/391阅
顶部