开发者

Trying to understand closures. Could somebody walk me through this code?

开发者 https://www.devze.com 2023-03-01 03:04 出处:网络
Here\'s a bit of javascript taken from this reddit post: function Stream() { var data= [], listeners= [];

Here's a bit of javascript taken from this reddit post:

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!" );

I think I have a tenuous grasp on Closures after reading this tutorial, but I just can't figure out how this code works. When I try to parse it in my head, I basically get stuck at line 6: var result = data.p开发者_运维问答ush( new_data );.

It seems with data simply being an array at that point data.push( foo ) doesn't make sense. And wouldn't it recurse infinitely anyway? (strike that -- didn't know there was a native push method for arrays) Very next line callListener is called with two parameters, but below the function has none.

If someone's got a few minutes, could you grab my hand and walk me through this code like the ignorant dolt I am? Right now, I'm not even sure I understand the destination.


Arrays are objects, and they have a push() method. Nothing unusual there.

The callListeners() function doesn't declare any named parameters, but JavaScript allows functions to be called with more parameters than they're declared to take, and the full list of arguments is available as the special name arguments. callListeners() uses arguments in an action.apply() call, to invoke the action function with the same list of arguments that callListeners() itself was given. The purpose of callListeners() is that you call it with some arguments, and it calls all the functions in the listeners array with those arguments.

Neither of those things is really related to the use of closures, though. The place where closures come into play is that the object returned by Stream() has two methods, push() and addListener(), that can "see" the same data and listeners arrays even though those arrays aren't stored in the object that the methods are called on. Two calls to Stream() will return two objects whose methods see different data and listeners arrays.


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

data is a reference to data in the outer function. The return value from data.push(...) is the length of the array after new_data has been added.

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

callListeners() is a call to the function declared with that name below.

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

This adds listener to listeners and returns the new length of the listeners array.

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

The above function calls all the listeners in the listeners array with the arguments that have been passed to it. It calls them in reverse order though (i.e. the last one added is called first, then the second last, and so on), which is a bit unusual.

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

Returns an object with properties push (whose value is a reference to the function declared with the name push), and addListener (whose value is a reference to the function declared with the name addListener)

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

By convention, functions whose name starts with a capital letter are constructors and should be called with the new operator. Stream is not a constructor so should start with a lower case s.

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

That calls foo.addListener (which is a reference to the "closed over" addListener function created when Stream() was called above) and passes it an anonymous function. Nothing is done with the return value.

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

That calls the (closed over) push function and passes it the string "Hello World!". push then calls callListeners(), passing it the same string. callListeners then calls every function in the listeners array (there's only one so far, the anonymous function added above), passing it the parameters supplied to push() (i.e. "Hello World!").

So the result is an alert with "added: Hello World!".

If you add another listener, then calling foo.push() will call both listeners with the supplied arguments, but in the reverse order to which they were added.


First of all, push is a method on the Array class:

Mutates an array by appending the given elements and returning the new length of the array.

So var result = data.push( new_data ); just appends new_data to data and sets result to the new number of elements in data.

The callListeners bit is tricky. The action will be a function and it will be called by using the apply method on that function (note: functions are objects in JavaScript). You'll also see arguments in there, that's a special variable that is:

An array-like object corresponding to the arguments passed to a function.

So, if you want to unpack the argument list yourself (like using @_ in Perl, or variable argument lists in C or C++) or if you're just planning on passing the full argument list to someone else that wants an array, then you can use arguments. Normally you'd say:

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

To explicitly convert the arguments to a real array. However, in this case apply is happy to take a raw arguments pseudo-array so there's no need for the "slice cast".


JavaScript arrays have a native push() method. Check out this article...

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

... so the code isn't calling function push( new_data ){} recursively, it is referring to the array's native push() method.

If it was recursive, it would instead be var result = this.push( new_data ); which would make it infinite :)

I hope this helps.
Hristo

0

精彩评论

暂无评论...
验证码 换一张
取 消