第四章 函数 Functions (二)

参数 arguments

arguments数组: 函数可以通过此参数访问所有它被调用时传递给它的参数列表,包括哪些没有被分配给函数声明时定义的形式参数的多余参数。

类似数组"(array-like)"的对象。arguments拥有一个length属性,没有任何数组方法。


返回 return

return被执行,函数立刻返回而不再执行余下的语句

  • 一个函数总是会返回一个值,没有指定返回值,则返回 undefined
  • 使用 new 调用函数,且返回值不是一个对象,则返回this (该新对象)

异常 Exceptions

异常是干扰程序的正常流程的不寻常(但并非完全是出乎意料的)事故

 try {
   if(false) {
     throw {
       name:"TypeError",
       message:"number is required"
     }
   }
 }catch(e) {
   document.write(e.name + ": "+e.message)
 }

throw 语句中断函数的运行。对象被传递到catch 从句中被捕获。


扩充类型的功能

通过给Function.prototype 增加方法,来使得该方法对所有函数可用:

   Function.prototype.method = function(name,func) {
     this.prototype[name] = func;
     return this;
   }

这样在函数,数组,字符串,数字,正则表达式和布尔值等基本类型的构造函数上添加方法时,就可以省去prototype 这几个字符。使得对应的所有变量都适用改方法

为Number类型加上一个取整方法

   Number.method('integer',function(){
       return Math[this<0?'ceil':'floor'](this);
     });

为String加上一个去除首尾空白的方法

  String.method("trim",function(){
      return this.replace(/^\s+|\s+$/g,'');
   })

与类库一起混用是,在确定没有该方法时才添加它

 // 符合条件时才增加方法
 Function.prototype.method = function(name,func) {
   if(!this.prototype[name]) {
     this.prototype[name] = func;
   }
   return this;
 }

递归

直接或者间接地调用自身的一种函数,它把一个问题分解为一组相似的子问题

经典的递归问题汉诺塔。塔上有3根柱子(1,2,3)和一套子直径各不相同的空心圆盘。
开始盘子从小到大的顺序堆叠在1号柱子上,目标是要经过2号柱子全部移动到3号柱子上,中间不允许较大的盘放在较小的盘上;

分解成子问题:

  • 将1号柱子上的从上到下的n-1个盘利用3号柱子移动到2号柱子上。
  • 将1号柱子上最下面的一个盘,直接移动到3号柱子上.
  • 最后把2号柱子上n-1个盘利用递归调用方法,全部移动到3号柱子上。
  var hanoi = function(disc,src,aux,dst) {
    if(disc > 0) {
      hanoi(disc -1,src,dst,aux);
      document.writeln('Move disc '+disc + " from "+src+"to "+dst);
      hanoi(disc-1,aux,src,dst)
    }
  };
  hanoi(3,'Src','Aux',"Dst");

  //
  Move disc 1 from Srcto Dst
  Move disc 2 from Srcto Aux
  Move disc 1 from Dstto Aux
  Move disc 3 from Srcto Dst
  Move disc 1 from Auxto Src
  Move disc 2 from Auxto Dst
  Move disc 1 from Srcto Dst

利用递归高效地操作树形结构,比如浏览器端的文档对象模型(DOM),

 var walk_the_DOM = function walk(node,func) {
   func(node);
   node = node.firstChild;
   while(node) {
     walk(node,func);
     node = node.nextSibling;
   }
 }

 // 定义 getElementsByAttribute 函数,
 //它以一个属性名称字符串和一个可选的匹配值作为参数。
 var getElementsByAttribute = function(att,value) {
   var results = [];
   walk_the_DOM(document.body,function(node) {
     var actual = node.nodeType === 1 $$ node.getAttribute(att);
     if(typeof actual === 'string' && (actual === value || typeof value != 'string')) {
       results.push(node)
     }
  });
  return results;
 }

一些语言提供了尾递归优化。这意味着如果一个函数返回自身递归调用的结果,那么调用的过程会被替换为一个循环,可以显著提高速度。

  //构建一个带尾递归的函数。因为它会返回自身调用的结果
  // 实现 factorial = n*(n-1)(n-2)... 1;
  var factorial = function factorial(i,a) {
    a = a || 1;
    if(i<2) {
      return a;
    }
    return factorial(i-1,a*i);
  };
  document.writeln(factorial(4))   // 4*3*2*1 = 24

作用域

作用域控制着变量与参数的可见性及生命周期

函数作用域: 定义在函数中的参数和变量在函数外部是不可见的,而在一个函数内部任何位置定义的变量,在该函数内部任何地方都可见。

在JavaScript中缺少会计作用域,最好的做法是在函数体的顶部声明函数中可能用到的所有变量。


闭包 Closure

闭包:函数可以访问它被创建时所处的上下文环境。

内部函数可以访问外部函数的实际变量而无需复制

作用域的好处是内部函数可以访问定义它们的外部函数的参数和变量(除了this 和arguments)

内部函数拥有比它的外部函数更长的生命周期。

  var myObject = (function(){
    var value = 0;
    return {
      increment:function(inc) {
        value += typeof inc === 'number' ?inc :1;
      },
      getValue:function() {
        return value;       
      }
    }

  })()

上面定义的value变量对increment和getValue方法总是可用的,但函数的作用域使得它对其他的程序来说是不可见的


回调 Callbacks

异步请求,提供一个服务器的响应到达是随即触发的回调函数,
异步函数立即返回。

模块 Module

使用 函数 和 闭包创造模块

模板的一般形式:

  • 一个定义了私有变量和函数的函数
  • 利用闭包创建可以访问私有变量和函数的特权函数
  • 返回这个特权函数,或者把它们保存到一个可访问到的地方。

利用前面使用的method方法,为String 增加一个deentityify 方法,寻找字符串中的HTML字符实体并把它们替换为对应的字符。

  String.method('deentityify',function(){
     // 字符实体表,它映射字符实体的名字到对应的字符
     var entity = {
       quot: '"',
       lt:'<',
       gt:'>'
     };
    // 返回 deetityify 方法
    return function() {
      // 下面就是deetityify方法,
      return this.replace(/&([^&;]+);/g,
          function(a,b){
            var r = entity[b];
            return typeof r === 'string'?r:a;
          }
      )
    }   
  }());

最后,使用()运算符立刻调用我们刚刚构造出来的函数,这个调用所创建并返回的函数才是deentityify方法

  document.writeln('&lt;&quot;&gt;'.deentityify())    // <">

级联

让执行后的函数返回this 而不是 undefined,就可以启用级联

一个级联中就,我们可以在单独一条语句中依次调用同一个对象的很多方法。一个启用了Ajax类库可能允许我们以这样的形式去编码

  getElement('myBoxDiv')
     .move(300,150)
     .width(100)
     .height(200)
     ...
     .tip('This box is resizeable')

上面的例子中,每次调用返回的结果都会被下一次调用所用。


柯里化

柯里化允许我们把函数与传递给它的参数相结合,产生一个新的函数
  Function.method('curry',function(){
    var slice = Array.prototype.slice,
        args = slice.apply(arguments),
        that = this;
    return function() {
      return that.apply(null,args.concat(slice.apply(arguments)))
    }
    })

 function add (a,b) {return a+b}
 add.curry(1)(6) //7

记忆

函数可以将先前操作的结果记录在某个对象里,从而避免无谓的重复运算,这种优化被称为记忆

以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('//'+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

改进,利用memo数组保存我们的存储结果,存储结果可以隐藏在闭包中。

  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;
  }();
  fibonacci(10)  //55

推而广之,编写一个函数来帮助我们构造带记忆功能的函数。

  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;
  }
  var fibonacci = memoizer([0,1],function(recur,n) {
      return recur(n-1) + recur(n-2);
    });
  fibonacci(10)  // 55

《JavaScript 语言精粹》读书笔记 - 函数


不可能的是
2.3k 声望123 粉丝

stay hungry&&foolish