理解closures:构造一个将函数排队在一起的元函数 [英] Understanding closures: Constructing a meta-function that queues functions together
问题描述
在解决问题的方面,我有一个完整的工作解决方案,我刚刚在这里:
In terms of solving the problem, I have a fully working solution that I just finished here:
// 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]();
}
我完全满意这个,因为它做了我想要的,可能比我的第一种方法更容易调试,如果它成功了,本来是。
I am completely happy with this because it does what I want now, and is probably far easier to debug than what my first approach, if it had succeeded, would have been.
但是我无法解决的是为什么我永远不能工作。
But what i can't get over is why I could never get it to work.
看起来像这样。
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();
}
它一直试图调用自己的无限循环,其他奇怪的事情,
It kept trying to call itself in an infinite loop, among other strange things, and it's really hard to identify which function a function is and what a function contains within it, during debugging.
我没有深入的代码,但似乎 jQuery .queue
也实现了类似的机制,我的工作(使用数组跟踪连续的队列),而不是只使用闭包。
I did not dig into the code, but it appears that jQuery.queue
has also implemented a similar mechanism to my working one (using an array to track the queue of continuations) rather than using only closures.
我的问题是:可以构建一个Javascript函数,它可以接受一个函数作为参数,并通过其他函数列表增强它,通过构建闭包它创建自己的wrap函数?
My question is this: Is it possible to build a Javascript function that can take a function as argument, and enhance it with a list of other functions, by building closures that wrap functions it creates itself?
这真的很难描述。但我相信有人有一个适当的理论支持的数学术语。
This is really hard to describe. But I'm sure somebody has a proper theory-backed mathematical term for it.
上述代码引用的是这些例程
P.S. Referenced by the code above are these routines
// 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
的值,并在中调用
回调。当你创建一个 cur_cont
c $ c> onload tmp_f
时,任何自由变量如 cur_cont
都不是 '到它们的当前值。如果 cur_cont
完全改变,则从 tmp_f
内引用它将引用新的更新值。由于您不断地将 cur_cont
更改为您刚刚创建的新的 tmp_f
函数,对其他函数的引用丢失。然后,当执行 cur_cont
并完成时,再次调用 cur_cont
。这是完全相同的函数,刚刚完成执行 - 因此无限循环!
The problem is how you were setting the value of cur_cont
for every new function you made, and calling cur_cont
in the onload
callback. When you make a closure like tmp_f
, any free variables like cur_cont
are not 'frozen' to their current values. If cur_cont
is changed at all, any reference to it from within tmp_f
will refer to the new, updated value. As you are constantly changing cur_cont
to be the new tmp_f
function you have just made, the reference to the other functions are lost. Then, when cur_cont
is executed and finishes, cur_cont
is called again. This is exactly the same function that had just finished executing - hence the infinite loop!
在这种情况下,你需要保持自由变量的值内一个闭包,最简单的事情是做一个新的函数,并调用你想要保留的值。通过调用这个新函数,只为该运行创建一个新变量,这将保持你需要的值。
In this sort of situation, where you need to keep the value of a free variable inside a closure, the easiest thing to do is to make a new function and call that with the value you want to keep. By calling this new function, a new variable is created just for that run, which will keep the value you need.
function js_load(resources, cb_done) {
var cur_cont = cb_done;
array_each_reverse(resources, function(r) {
// the stack of callbacks must be assembled in reverse order
// Make a new function, and pass the current value of the `cur_cont`
// variable to it, so we have the correct value in later executions.
// Within this function, use `done` instead of `cur_cont`;
cur_cont = (function(done) {
// Make a new function that calls `done` when it is finished, and return it.
// This function will become the new `cur_cont`.
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();
};
};
})(cur_cont);
});
// Start executing the function chain
cur_cont();
}
EDIT:实际上,使用 Array.reduce
函数。在概念上,你正在获取一个数组,并从该数组产生一个单一的函数,并且每个连续的函数生成应该依赖于最后生成的函数。这是reduce旨在帮助解决的问题:
Actually, this can be made even simpler by using the Array.reduce
function. Conceptually, you are taking an array and producing a single function from that array, and each successive function generated should be dependant upon the last function generated. This is the problem that reduce was designed to help solve:
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();
};
注意 reduce
和 reduceRight
不适用于旧版浏览器(< = IE8)。 JavaScript实现可以在 MDN页面上找到。
Note that reduce
and reduceRight
are not available for older browsers (<= IE8). A JavaScript implementation can be found on the MDN page.
这篇关于理解closures:构造一个将函数排队在一起的元函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!