试图了解闭包。有人可以告诉我这段代码吗?

时间:2011-04-27 05:03:59

标签: javascript closures

以下是从this reddit post获取的一些javascript:

function Stream() {
    var data       = [],
        listeners  = [];

    function push( new_data ) {
        var result = data.push( new_data );
        callListeners( new_data, result );
        return result;
    }

    function addListener( listener ) {
        return listeners.push( listener );
    }

    function callListeners( ) {
        var length    = listeners.length,
            result    = [],
            action    = null;
        while ( length-- ) {
            action = listeners[ length ];
            result.push( action.apply( null, arguments) );
        }
        return result;
    }

    return {
        push : push,
        addListener: addListener
    }

}


var foo = Stream();
foo.addListener( function( new_data ) {
    alert( "added: " + new_data );
});
foo.push( "Hello World!" );

我认为在阅读this tutorial之后,我对封闭有一个微妙的把握,但我无法弄清楚这段代码的工作原理。当我试图在脑海中解析它时,我基本上陷入第6行:var result = data.push( new_data );

似乎data只是在那一点上成为一个数组data.push( foo )没有意义。并且它不会无限地递归吗?(罢工 - 不知道数组有一个本地push方法)下一行callListener被调用两个参数,但是函数下面没有。

如果有人有几分钟的话,你能不能抓住我的手,像我无知的傻瓜一样引导我完成这些代码?现在,我甚至不确定我是否了解目的地。

4 个答案:

答案 0 :(得分:4)

数组对象,它们有push()方法。没有什么不寻常的。

callListeners()函数没有声明任何命名参数,但JavaScript允许使用比声明参数更多的参数调用函数,并且完整的参数列表可用作特殊名称{{ 1}}。 argumentscallListeners()调用中使用arguments,使用action.apply()本身给出的相同参数列表调用action函数。 callListeners()的目的是用一些参数调用它,并使用这些参数调用callListeners()数组中的所有函数。

但是,这些事情都与闭包的使用无关。闭包发挥作用的地方是listeners返回的对象有两种方法Stream()push(),可以“看到”相同的addListener()和{{1即使这些数组没有存储在调用方法的对象中,也是如此。对data的两次调用将返回两个对象,其方法会看到不同的 listenersStream()数组。

答案 1 :(得分:3)

> function Stream() {
>     var data       = [],
>         listeners  = [];
> 
>     function push( new_data ) {
>         var result = data.push( new_data );

data是对外部函数中数据的引用。 data.push(...)的返回值是添加new_data后数组的长度。

>         callListeners( new_data, result );
>         return result;
>     }

callListeners()是对下面用该名称声明的函数的调用。

>     function addListener( listener ) {
>         return listeners.push( listener );
>     }

这会将listener添加到listeners并返回listeners数组的新长度。

>     function callListeners( ) {
>         var length    = listeners.length,
>             result    = [],
>             action    = null;
>         while ( length-- ) {
>             action = listeners[ length ];
>             result.push( action.apply( null, arguments) );
>         }
>         return result;
>     }

上面的函数使用已传递给它的参数调用listeners数组中的所有侦听器。它虽然以相反的顺序调用它们(即最后一个被称为第一个,然后是最后一个,依此类推),这有点不寻常。

>     return {
>         push : push,
>         addListener: addListener
>     }

返回一个属性为push的对象(其值是对使用名称​​ push 声明的函数的引用)和addListener(其值是对使用名称​​ addListener )声明的函数

> }
> 
> 
> var foo = Stream();

按照惯例,名称以大写字母开头的函数是构造函数,应该使用new运算符调用。 Stream 不是构造函数,所以应该以小写s开头。

> foo.addListener( function( new_data )
> {
>     alert( "added: " + new_data ); });

调用foo.addListener(这是对上面调用Stream()时创建的“关闭” addListener 函数的引用)并将其传递给匿名函数。返回值没有任何作用。

> foo.push( "Hello World!" );

调用(关闭)push函数并将其传递给字符串“Hello World!”。 push然后调用callListeners(),传递相同的字符串。 callListeners然后调用侦听器数组中的每个函数(到目前为止只有一个函数,上面添加了匿名函数),并将提供给push()的参数传递给它(即“Hello World!”)。

所以结果是一个警告“添加:Hello World!”。

如果添加另一个侦听器,则调用foo.push()将使用提供的参数调用两个侦听器,但顺序与它们的添加顺序相反。

答案 2 :(得分:2)

首先,push是Array类的一个方法:

  

通过附加给定元素并返回数组的新长度来变异数组。

所以var result = data.push( new_data );只需将new_data追加到data,并将result设置为data中的新元素数。

callListeners位很棘手。 action将是一个函数,它将通过在该函数上使用apply方法来调用(注意:函数是JavaScript中的对象)。你还会在那里看到arguments,这是一个特殊的变量:

  

与传递给函数的参数对应的类数组对象。

所以,如果你想自己解压缩参数列表(比如在Perl中使用@_,或者在C或C ++中使用变量参数列表),或者你只是计划将完整的参数列表传递给其他人想要一个数组,那么你可以使用arguments。通常你会说:

var a = Array.prototype.slice.call(arguments);

arguments显式转换为真实数组。但是,在这种情况下,apply很乐意采用原始arguments伪数组,因此不需要“切片转换”。

答案 3 :(得分:1)

JavaScript数组具有本机push()方法。看看这篇文章......

http://www.hunlock.com/blogs/Mastering_Javascript_Arrays

...所以代码不是递归地调用function push( new_data ){},而是指数组的本机push()方法。

如果它是递归的,那么它将是var result = this.push( new_data );,这将使它无限:)

我希望这会有所帮助 赫里斯托斯