JS编程建议——66:使用函数实现历史记录

建议66:使用函数实现历史记录
函数可以利用对象去记住先前操作的结果,从而能避免无谓的运算,这种优化称为记忆。JavaScript的对象和数组要实现这种优化是非常方便的。
例如,使用递归函数计算fibonacci数列。一个fibonacci数字是之前两个fibonacci数字之和。最前面的两个数字是0和1。
var fibonacci = function(n) {

return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);

};
for(var i = 0; i <= 10; i += 1) {

document.writeln('<br>' + i + ': ' + fibonacci(i));

}
返回下列值:
0: 0
1: 1
2: 1
3: 2
4: 3
5: 5
6: 8
7: 13
8: 21
9: 34
10: 55
在上面代码中,fibonacci函数被调用了453次,其中循环调用了11次,它自身调用了442次,去计算可能已刚计算过的值。如果使该函数具备记忆功能,就可以显著减少它的运算次数。
先使用一个临时数组保存存储结果,存储结果可以隐藏在闭包中。当函数被调用时,先看是否已经知道存储结果,如果已经知道,就立即返回这个存储结果。
var fibonacci = ( function() {

var memo = [0, 1];
var fib = function(n) {
    var result = memo[n];
    if( typeof result !== 'number') {
        result = fib(n - 1) + fib(n - 2);
        memo[n] = result;
    }
    return result;
};
return fib;

}());
for(var i = 0; i <= 10; i += 1) {

document.writeln('<br>' + i + ':' + fibonacci(i));

}
这个函数返回同样的结果,但它只被调用了29次,其中循环调用了11次,它自身调用了18次,去取得之前存储的结果。当然,可以把这种函数形式抽象化,以构造带记忆功能的函数。memoizer函数将取得一个初始的memo数组和fundamental函数。memoizer函数返回一个管理memo存储和在需要时调用fundamental函数的shell函数。memoizer函数传递这个shell函数和该函数的参数给fundamental函数。
var memoizer = function(memo, formula) {

var recur = function(n) {
    var result = memo[n];
    if( typeof result !== 'number') {
        result = formula(recur, n);
        memo[n] = result;
    }
    return result;
};
return recur;

};
现在,就可以使用memoizer来定义fundamental函数,提供初始的memo数组和fundamental函数。
var fibonacci = memoizer([0, 1], function(recur, n) {

return recur(n - 1) + recur(n - 2);

});
通过设计能产生其他函数的函数,可以极大地减少一些不必要的工作。例如,要产生一个可记忆的阶乘函数,只需提供基本的阶乘公式即可。
var factorial = memoizer([1, 1], function(recur, n) {

return n * recur(n - 1);

});

时间: 2024-07-30 02:26:11

JS编程建议——66:使用函数实现历史记录的相关文章

JS编程建议——73:函数绑定有价值

建议73:函数绑定有价值函数绑定就是为了纠正函数的执行上下文,特别是当函数中带有this关键字的时候,这一点尤其重要,稍微不小心,就会使函数的执行上下文发生跟预期不同的改变,导致代码执行上的错误.函数绑定具有3个特征:函数绑定要创建一个函数,可以在特定环境中以指定参数调用另一个函数.一个简单的bind()函数接收一个函数和一个环境,返回一个在给定环境中调用给定函数的函数,并且将所有参数原封不动地传递过去.被绑定函数与普通函数相比有更多的开销,它们需要更多内存,同时也因为多重函数调用而稍微慢一点,

JS编程建议——75:函数柯里化

建议75:函数柯里化柯里化是把接受多个参数的函数变换成接受一个单一参数的函数,并且返回一个新函数,这个新函数能够接受原函数的参数.下面可以通过例子来帮助理解.function adder(num) { return function(x) { return num + x; } }var add5 = adder(5);var add6 = adder(6);print(add5(1)); // 6print(add6(1)); //7函数adder接受一个参数,并返回一个函数,这个返回的函数可

JS编程建议——59:推荐动态调用函数

建议59:推荐动态调用函数调用函数更便捷的方式是使用Function对象的call和apply方法.apply与call方法在本质上没有太大区别,只不过它们传递给函数的参数方式不同, apply是以数组形式进行参数传递,而call方法可以同时传递多个值.如果某个函数仅能够接收多个参数列表,而现在希望把一个数组的所有元素作为参数进行传递,那么使用apply方法就显得非常便利.function max(){ var m = Number.NEGATIVE_INFINITY; // 声明一个负无穷大的

JS编程建议——74:使用高阶函数

建议74:使用高阶函数高阶函数作为函数式编程众多风格中的一项显著特征,经常被使用.实际上,高阶函数即对函数的进一步抽象.高阶函数至少满足下列条件之一:接受函数作为输入.输出一个函数. 在函数式语言中,函数不但是一种特殊的对象,还是一种类型,因此函数本身是一个可以传来传去的值.也就是说,某个函数在刚开始执行的时候,总可以送入一个函数的参数.传入的参数本身就是一个函数.当然,这个输入的函数相当于某个函数的另外一个函数.当函数执行完毕之后,又可以返回另外一个新的函数,这个返回函数取决于return f

JS编程建议——67:套用函数

建议67:套用函数套用是JavaScript函数一个很有趣的应用.所谓套用就是将函数与传递给它的参数相结合,产生一个新的函数.在函数式编程中,函数本身也是一个值,这种特性允许用户以有趣的方式去操作函数值.例如,在下面代码中定义一个add()函数,该函数能够返回一个新的函数,并把参数值传递给这个新函数,从而实现连加操作.var add = function(n){ return function(m){ return n+m; } }document.writeln(add(2)(3)); //5

JS编程建议——22:少用函数迭代

建议22:少用函数迭代ECMA-262v4为本地数组对象新增加了一个forEach方法.此方法遍历一个数组的所有成员,并且在每个成员上执行一个函数.在每个元素上执行的函数作为forEach()的参数传进去,并在调用函数时接收3个参数:数组项的值.数组项的索引.数组自身.例如: items.forEach(function(value, index, array){ process(value); }); forEach在Firefox.Chrome和Safari等浏览器中为原生函数.另外,for

JS编程建议——72:惰性载入函数

建议72:惰性载入函数惰性载入函数主要解决的问题也是兼容性,原理跟分支函数类似,下面是简单的示例.var addEvent = function(el, type, handle) { addEvent = el.addEventListener ? function(el, type, handle) { el.addEventListener(type, handle, false); } : function(el, type, handle) { el.attachEvent("on&q

JS编程建议——71:推荐分支函数

建议71:推荐分支函数分支函数解决的一个问题是浏览器之间兼容性的重复判断.解决浏览器之间的兼容性的一般方式是使用if逻辑来进行特性检测或能力检测,根据浏览器不同的实现来实现功能上的兼容,这样做的问题是,每执行一次代码,可能都需要进行一次浏览器兼容性方面的检测,这是没有必要的.能否在代码初始化执行的时候就检测浏览器的兼容性,在之后的代码执行过程中,就无须再进行检测了呢?答案是:能.分支技术就可以解决这个问题,下面以声明一个XMLHttpRequest实例对象为例进行介绍.var XHR = fun

JS编程建议——76:要重视函数节流

建议76:要重视函数节流比起非DOM交互,DOM操作需要更多内存和CPU时间.连续尝试进行过多的DOM相关操作可能会导致浏览器变慢甚至崩溃.函数节流的设计思想就是让某些代码可以在间断情况下连续重复执行,实现的方法是使用定时器对函数进行节流.例如,在第一次调用函数时,创建一个定时器,在指定的时间间隔后执行代码.当第二次调用时,清除前一次的定时器并设置另一个,实际上就是前一个定时器演示执行,将其替换成一个新的定时器.var processor = { timeoutId : null, //实际进行