JS编程建议——32:使用制表

建议32:使用制表
代码所做的事情越少,它的运行速度就越快,因此,避免重复工作很有意义。多次执行相同的任务也在浪费时间。制表法通过缓存先前计算结果为后续计算所使用,避免了重复工作,这使得制表成为递归算法中最有用的技术。
当递归函数被多次调用时,重复工作很多。以下factorial()函数是一个递归函数重复多次的典型例子。

  1. var fact6 = factorial(6);
  2. var fact5 = factorial(5);
  3. var fact4 = factorial(4);
    此代码生成3个阶乘结果,factorial()函数总共被调用了18次。此代码中最糟糕的部分是,所有必要的计算已经在第一行代码中执行过了。因为6的阶乘等于6乘以5 的阶乘,所以5的阶乘被计算了两次。更糟糕的是,4的阶乘被计算了3次。更为明智的方法是保存并重利用它们的计算结果,而不是每次都重新计算整个函数。使用制表法重写factorial()函数:
  4. function memfactorial(n) {
  5. if(!memfactorial.cache) {
  6. memfactorial.cache = {
  7. "0" : 1,
  8. "1" : 1
  9. };
  10. }
  11. if(!memfactorial.cache.hasOwnProperty(n)) {
  12. memfactorial.cache[n] = n * memfactorial(n – 1);
  13. }
  14. return memfactorial.cache[n];
  15. }
    使用制表法设计阶乘函数的关键是建立一个缓存对象,此对象位于函数内部,其中预置了两个最简单的阶乘:0 和1。在计算阶乘之前,先检查缓存中是否已经存在相应的计算结果。没有对应的缓冲值说明这是第一次计算此数值的阶乘,计算完成之后结果被存入缓存之中,以备今后使用。
  16. var fact6 = memfactorial(6);
  17. var fact5 = memfactorial(5);
  18. var fact4 = memfactorial(4);
    上面代码返回3个不同的阶乘值,总共只调用memfactorial()函数8次。既然所有必要的计算都在第一行代码中完成了,那么后两行代码不会产生递归运算,因为直接返回了缓存中的数值。制表过程会因递归函数的种类不同而略有不同,但总体上具有相同的模式。
时间: 2024-09-02 09:31:05

JS编程建议——32:使用制表的相关文章

JS编程建议——3:减少全局变量污染

建议3:减少全局变量污染定义全局变量有3种方式:在任何函数外面直接执行var语句. var f = 'value'; 直接添加一个属性到全局对象上.全局对象是所有全局变量的容器.在Web浏览器中,全局对象名为window. window.f = 'value'; 直接使用未经声明的变量,以这种方式定义的全局变量被称为隐式的全局变量. f = 'value'; 为方便初学者在使用前无须声明变量而有意设计了隐式的全局变量,然而不幸的是忘记声明变量成了一个非常普遍的现象.JavaScript的策略是让

JS编程建议——8:谨慎使用运算符(1)

建议8:谨慎使用运算符(1)1.用===,而不用==JavaScript有两组相等运算符:===和!==.==和!=.===和!==这一组运算符会按照期望的方式工作.如果两个运算数类型一致且拥有相同的值,那么===返回true,而!==返回false.==和!=只有在两个运算数类型一致时才会做出正确的判断,如果两个运算数是不同的类型,会试图强制转换运算数的类型.转换的规则复杂且难以记忆,具体规则如下: '' == '0' // false 0 == '' // true 0 == '0' //

JS编程建议——16:防止switch贯穿

建议16:防止switch贯穿JavaScript语言中那些显而易见的危险或无用的特性不是最糟糕的,这些特性很容易被避免.最糟糕的特性像"带刺的玫瑰",它们是有用的,但也是危险的.switch语句的由来可以追溯到FORTRAN IV的go to语句.除非明确地中断流程,否则每次条件判断后都贯穿到下一个case条件.switch语句的基本语法格式如下: switch (expression ){ statements } 完全扩展后的switch结构如下: switch ( expres

JS编程建议——29:准确使用循环体(1)

建议29:准确使用循环体(1)1.选择正确的循环体在大多数编程语言中,代码执行时间多数消耗在循环的执行上.在一系列编程模式中,循环是最常用的模式之一,因此也是提高性能必须关注的地方之一.理解JavaScript 中循环对性能的影响至关重要,因为死循环或长时间运行的循环会严重影响用户体验.JavaScript定义了4种类型的循环:第一种循环是标准的for循环,与C语言使用同样的语法: for (var i=0; i < 10; i++){ //循环体 } for循环是最常用的循环结构,它由4部分组

JS编程建议——36:警惕字符串连接操作(2)

建议36:警惕字符串连接操作(2)先将两个小字符串合并起来,然后将结果返回给大字符串.创建中间字符串s1 + s2与两次复制大字符串相比,对性能的"冲击"要轻得多.(2)编译期合并在赋值表达式中所有字符串连接都属于编译期常量,Firefox自动地在编译过程中合并它们.在以下这个方法中可看到这一过程: function foldingDemo() { var str = "compile" + "time" + "folding"

JS编程建议——30:使用递归模式

建议30:使用递归模式复杂算法通常比较容易使用递归实现.很多传统算法正是通过递归实现的,如阶乘函数. function factorial(n) { if(n == 0) { return 1; } else { return n * factorial(n – 1); } } 递归函数的问题:错误定义或缺少终结条件会导致函数长时间运行,使浏览器出现假死现象.此外,递归函数还会受到浏览器调用栈大小的限制. JavaScript引擎所支持的递归数量与JavaScript调用栈大小直接相关.只有IE

JS编程建议——50:正确检测数组类型

建议50:正确检测数组类型由于数组和对象的数据同源性,导致在JavaScript编程中经常会出现:在必须使用数组时使用了对象,或者在必须使用对象时使用了数组.选用数组或对象的规则很简单:当属性名是小而连续的整数时,应该使用数组,或者当对属性的位置和排列顺序有要求时,应该使用数组.否则,使用对象.JavaScript语言对数组和对象的区别是混乱的.typeof运算符检测数组的类型是"object",这没有什么意义,因此在正确检测数组和对象方面JavaScript没有提供很多的机制.这时可

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

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

JS编程建议——65:比较函数的惰性求值与非惰性求值

建议65:比较函数的惰性求值与非惰性求值在JavaScript中,使用函数式风格编程时,应该对于表达式有着深刻的理解,并能够主动使用表达式的连续运算来组织代码.1)在运算元中,除了JavaScript默认的数据类型外,函数也作为一个重要的运算元参与运算.2)在运算符中,除了JavaScript的大量预定义运算符外,函数还作为一个重要的运算符进行计算和组织代码.函数作为运算符参与运算,具有非惰性求值特性.非惰性求值行为自然会对整个程序产生一定的负面影响.先看下面这个示例:var a = 2;fun