理解闭包:构造一个将函数排列在一起的元函数



在解决这个问题方面,我有一个完全可行的解决方案,我刚刚在这里完成:

// synchronous dynamic script loading. 
// takes an array of js url's to be loaded in that specific order. 
// assembles an array of functions that are referenced more directly rather than 
// using only nested closures. I couldn't get it going with the closures and gave up on it. 
function js_load(resources, cb_done) {
    var cb_list = []; // this is not space optimal but nobody gives a damn 
    array_each(resources, function(r, i) {
        cb_list[i] = function() {
            var x = document.body.appendChild(document.createElement('script'));
            x.src = r;
            console.log("loading "+r);
            x.onload = function() { 
                console.log("js_load: loaded "+r); 
                if (i === resources.length-1) {
                    cb_done();
                } else {
                    cb_list[i+1]();
                }
            }; 
        };
    });
    cb_list[0]();
}

我对此非常满意,因为它现在做了我想要的,并且可能比我的第一种方法更容易调试,如果它成功了,就会是。

但我无法理解的是为什么我永远不能让它工作。

它看起来像这样。

function js_load(resources, cb_done) {
    var cur_cont = cb_done;
    // So this is an iterative approach that makes a nested "function stack" where 
    // the inner functions are hidden inside the closures. 
    array_each_reverse(resources, function(r) {
        // the stack of callbacks must be assembled in reverse order
        var tmp_f = function() {
            var x = document.body.appendChild(document.createElement('script'));
            x.src = r;
            console.log("loading "+r);
            x.onload = function() { console.log("js_load: loaded "+r); cur_cont(); }; // TODO: get rid of this function creation once we know it works right 
        };
        cur_cont = tmp_f; // Trying here to not make the function recursive. We're generating a closure with it inside. Doesn't seem to have worked :(
    });
    cur_cont();
}

它一直试图在无限循环中调用自己,在其他奇怪的事情中,在调试期间很难识别函数是哪个函数以及函数中包含什么。

我没有深入研究代码,但似乎jQuery.queue也实现了与我的工作机制类似的机制(使用数组来跟踪延续队列),而不是仅使用闭包。

我的问题是:是否有可能构建一个Javascript函数,可以将函数作为参数,并通过构建闭包来包装它自己创建的函数,并用其他函数列表来增强它?

这真的很难描述。但我肯定有人有合适的理论支持的数学术语来形容它。

注:以上代码引用的是这些例程

// iterates through array (which as you know is a hash), via a for loop over integers
// f receives args (value, index)
function array_each(arr, f) {
    var l = arr.length; // will die if you modify the array in the loop function. BEWARE
    for (var i=0; i<l; ++i) {
        f(arr[i], i);
    }
}
function array_each_reverse(arr, f) {
    var l = arr.length; // will die if you modify the array in the loop function. BEWARE
    for (var i=l-1; i>=0; --i) {
        f(arr[i], i);
    }
}

问题是您如何为您所做的每个新函数设置cur_cont的值,并在onload回调中调用cur_cont。当你创建一个像tmp_f这样的闭包时,任何像cur_cont这样的自由变量都是而不是被"冻结"到它们的当前值。如果更改了cur_cont,那么从tmp_f中对它的任何引用都将引用更新后的新值。由于您不断地将cur_cont更改为刚刚创建的新tmp_f函数,因此丢失了对其他函数的引用。然后,当cur_cont执行完毕后,再次调用cur_cont。这与刚刚执行完的函数完全相同——因此是无限循环!

在这种情况下,需要将自由变量的值保存在闭包中,最简单的方法是创建一个新函数,并使用想要保留的值调用该函数。通过调用这个新函数,将为该运行创建一个新变量,该变量将保留您需要的值。

<>之前js_load(resources, cb_done) {Var cur_cont = cb_done;Array_each_reverse(资源,函数(r) {//回调堆栈必须以相反的顺序组装//创建一个新函数,并传递cur_cont的当前值//变量,以便在以后的执行中得到正确的值。//在这个函数中,使用' done '代替' cur_cont ';Cur_cont = (function(done)) {//创建一个新函数,当它完成时调用' done ',并返回它。//这个函数将成为新的' cur_cont '。返回函数(){var x = document.body.appendChild(document.createElement('script'));X.src = r;console.log("加载"+ r);X.onload = function() {Console.log ("js_load: loaded "+r);(完成);};};}) (cur_cont);});//开始执行函数链cur_cont ();}之前

EDIT:实际上,通过使用Array.reduce函数可以使这更简单。从概念上讲,您正在获取一个数组并从该数组中生成单个函数,并且每个后续生成的函数都应该依赖于最后生成的函数。这就是reduce被设计用来帮助解决的问题:

function js_load(resources, done) {
    var queue = resources.reduceRight(function(done, r) {
        return function() {
            var x = document.body.appendChild(document.createElement('script'));
            x.src = r;
            console.log("loading "+r);
            x.onload = function() {
                console.log("js_load: loaded "+r);
                done();
            };
        };
    }, done);
    queue();
};

请注意,reducereduceRight不适用于较旧的浏览器(<= IE8)。JavaScript实现可以在MDN页面上找到。

最新更新