我认为延续是回调的特例。一个函数可以回调任意数量的函数,任意次数。例如:
var array = [1, 2, 3];
forEach(array, function (element, array, index) {
array[index] = 2 * element;
});
console.log(array);
function forEach(array, callback) {
var length = array.length;
for (var i = 0; i < length; i++)
callback(array[i], array, i);
}
但是,如果一个函数调用另一个函数作为它所做的最后一件事,那么第二个函数被称为第一个函数的延续。例如:
var array = [1, 2, 3];
forEach(array, function (element, array, index) {
array[index] = 2 * element;
});
console.log(array);
function forEach(array, callback) {
var length = array.length;
// This is the last thing forEach does
// cont is a continuation of forEach
cont(0);
function cont(index) {
if (index < length) {
callback(array[index], array, index);
// This is the last thing cont does
// cont is a continuation of itself
cont(++index);
}
}
}
如果一个函数调用另一个函数作为它做的最后一件事,那么它被称为尾部调用。某些语言(如 Scheme)执行尾部调用优化。这意味着尾部调用不会产生函数调用的全部开销。相反,它被实现为一个简单的goto(调用函数的堆栈帧被尾部调用的堆栈帧替换)。
奖励:继续传球风格。请考虑以下程序:
console.log(pythagoras(3, 4));
function pythagoras(x, y) {
return x * x + y * y;
}
现在,如果每个操作(包括加法,乘法等)都是以函数的形式编写的,那么我们将拥有:
console.log(pythagoras(3, 4));
function pythagoras(x, y) {
return add(square(x), square(y));
}
function square(x) {
return multiply(x, x);
}
function multiply(x, y) {
return x * y;
}
function add(x, y) {
return x + y;
}
此外,如果不允许我们返回任何值,那么我们将不得不使用如下所示的延续:
pythagoras(3, 4, console.log);
function pythagoras(x, y, cont) {
square(x, function (x_squared) {
square(y, function (y_squared) {
add(x_squared, y_squared, cont);
});
});
}
function square(x, cont) {
multiply(x, x, cont);
}
function multiply(x, y, cont) {
cont(x * y);
}
function add(x, y, cont) {
cont(x + y);
}
这种不允许返回值的编程样式(因此必须求助于传递延续)称为延续传递样式。
但是,延续传递样式存在两个问题:
- 传递延续会增加调用堆栈的大小。除非您使用像 Scheme 这样的语言来消除尾部调用,否则您将面临堆栈空间不足的风险。
- 编写嵌套函数很痛苦。
第一个问题可以通过异步调用延续在 JavaScript 中轻松解决。通过异步调用延续,函数在调用延续之前返回。因此,调用堆栈大小不会增加:
Function.prototype.async = async;
pythagoras.async(3, 4, console.log);
function pythagoras(x, y, cont) {
square.async(x, function (x_squared) {
square.async(y, function (y_squared) {
add.async(x_squared, y_squared, cont);
});
});
}
function square(x, cont) {
multiply.async(x, x, cont);
}
function multiply(x, y, cont) {
cont.async(x * y);
}
function add(x, y, cont) {
cont.async(x + y);
}
function async() {
setTimeout.bind(null, this, 0).apply(null, arguments);
}
第二个问题通常使用称为 的函数来解决,该函数通常缩写为 。不幸的是,不能在JavaScript中完全实现,但是我们可以为其大多数用例编写一个替换函数:call-with-current-continuation
callcc
callcc
pythagoras(3, 4, console.log);
function pythagoras(x, y, cont) {
var x_squared = callcc(square.bind(null, x));
var y_squared = callcc(square.bind(null, y));
add(x_squared, y_squared, cont);
}
function square(x, cont) {
multiply(x, x, cont);
}
function multiply(x, y, cont) {
cont(x * y);
}
function add(x, y, cont) {
cont(x + y);
}
function callcc(f) {
var cc = function (x) {
cc = x;
};
f(cc);
return cc;
}
该函数采用函数并将其应用于 (缩写为 )。是一个延续函数,它在调用 后包装函数体的其余部分。callcc
f
current-continuation
cc
current-continuation
callcc
考虑函数的主体:pythagoras
var x_squared = callcc(square.bind(null, x));
var y_squared = callcc(square.bind(null, y));
add(x_squared, y_squared, cont);
第二个是:current-continuation
callcc
function cc(y_squared) {
add(x_squared, y_squared, cont);
}
同样,第一个是:current-continuation
callcc
function cc(x_squared) {
var y_squared = callcc(square.bind(null, y));
add(x_squared, y_squared, cont);
}
由于第一个包含另一个,因此必须将其转换为连续传递样式:current-continuation
callcc
callcc
function cc(x_squared) {
square(y, function cc(y_squared) {
add(x_squared, y_squared, cont);
});
}
因此,从本质上讲,从逻辑上讲,将整个函数体转换回我们开始的内容(并为这些匿名函数命名)。使用 callcc 的这种实现的毕达哥拉斯函数变为:callcc
cc
function pythagoras(x, y, cont) {
callcc(function(cc) {
square(x, function (x_squared) {
square(y, function (y_squared) {
add(x_squared, y_squared, cont);
});
});
});
}
同样,你不能在JavaScript中实现,但你可以在JavaScript中实现它作为延续传递样式,如下所示:callcc
Function.prototype.async = async;
pythagoras.async(3, 4, console.log);
function pythagoras(x, y, cont) {
callcc.async(square.bind(null, x), function cc(x_squared) {
callcc.async(square.bind(null, y), function cc(y_squared) {
add.async(x_squared, y_squared, cont);
});
});
}
function square(x, cont) {
multiply.async(x, x, cont);
}
function multiply(x, y, cont) {
cont.async(x * y);
}
function add(x, y, cont) {
cont.async(x + y);
}
function async() {
setTimeout.bind(null, this, 0).apply(null, arguments);
}
function callcc(f, cc) {
f.async(cc);
}
该函数可用于实现复杂的控制流结构,如试模块、协程、发生器、光纤等。callcc