JavaScript语言精粹-函数篇

60 阅读6分钟

这篇文章主要是记录《JavaScript语言精粹》函数篇的笔记。

函数对象

JavaScript中的函数就是对象。对象字面量产生的对象连接到object.prototype。函数连接到Function.prototype。每个函数对象在创建的时随配有一个prototype属性。它的值是一个拥有constructor属性且值即为该函数的对象。

函数字面量

函数对象通过函数字面量来创建,函数字面量包括四个部分:

  • 保留字function
  • 函数名,可以被省略,可以用来递归调用
  • 函数参数,用逗号分隔
  • 函数主体,用花括号包围
函数可以被定义在其他函数中,一个内部函数除了可以访问自己的变量和参数,同时可以访问把他嵌套在内的父函数的参数和变量。通过函数字面量创建的函数对象包含连到外部上下文的链接,这被称为闭包。

调用

除了声明时定义的形式参数,每个函数还接收两个附加的参数:this和arguments。参数this在面向对象编程中非常重要,它的值取决于调用模式。JavaScript中有四种调用模式:方法调用,函数调用,构造器调用和apply调用。

  • 方法调用模式 当一个函数被保存为对象的一个属性,我们称它为一个方法。当一个方法被调用时,this被绑定到该对象。
// 创建myObject对象,它有一个value属性和一个increment方法
        var myObject={
          value:0,
          increment:function(inc){
            this.value+typeof inc==='number'?inc:1
          }
        }
        // 方法可以使用this访问自己所属的对象,所以它能从对象中取值或对对象进行修改。
        //this到对象的绑定发生在调用的时候,这个超级延迟绑定使得函数可以对this高度复用。
        //通过this可取得他们所属对象的上下文的方法称为公共方法。
  • 函数调用模式 当一个函数并非一个对象的属性时,那么它就是被当作一个函数来调用的:
var sum=add(3,4); //sum的值为7
此模式下this被绑定到全局对象。

解决方案:如果该方法定义一个变量并给它赋值为this,那么内部函数可以通过那个变量访问到this。

myObject.double=function(){
          var that=this; //解决办法
          var helper=function(){
            that.value=add(that.value,that.value)
          }
          helper() //以函数的形式调用
        }
  • 构造器调用模式 如果在一个函数前面带上new来调用,那么背地里将会创建一个连接到该函数的prototype成员的新对象,同时this被绑定到那个新对象上。
var Quo=function(string){
          this.status=string
        }
        // 给Quo的所有实例对象都提供一个名为get_status的公共方法
        Quo.prototype.get_status=function(){
          return this.status;
        }
        // 构造一个Quo实例
        var myQuo=new Quo('confused')
        console.log(myQuo.get_status());

调用构造器函数的时候大写约定是非常重要的。

  • apply调用 作用:让我们构造一个参数数组传递给调用函数。它也允许我们选择this的值,apply两个参数,第一个是要绑定的this值,第二个是一个参数数组。

异常

JavaScript提供了一套异常处理机制。异常是干扰程序的正常流程的不寻常的事故。当发现这样的事故时,你的程序应该抛出一个异常:

var add=function(a,b){
          if(typeof a !=='number' || typeof b !=='number'){
            throw{
              name:'TypeError',
              message:'add needs numbers'
            }
          }
          return a+b;
        }
        // throw语句中断函数执行。它应该抛出一个exception对象,
        //该对象包含一个用来识别异常类型的name属性和一个描述性的message属性。也可以添加其他的属性
        // 该exception对象将被传递到一个try语句的catch从句:
        // 构造一个try_it函数
        var try_it=function(){
          try {
            add('seven')
          }catch(e){
            console.log(e.name+':'+e.message);
          }
        }
        try_it()

扩充类型的功能

JavaScript允许给语言的基本类型扩充功能。通过给Object.prototype添加方法,可以让该方法对所有的对象都可用。这样的方式对函数,数组,字符串,数字,正则表达式和布尔值同样适用。

        // 通过给Function.prototype增加方法来使得该方法对所有函数可用:
        Function.prototype.method=function(name,func){
          if(!this.prototype[name]){
            this.prototype[name]=func;
          }
          return this;
        }
        // 我们给Number.prototype增加一个interger方法:
        Number.method('integer',function(){
          return Math[this<0?'ceil':'floor'](this)
        })
        console.log((-10/3).integer());//-3

作用域

作用域控制着变量与参数的可见性及生命周期。它减少了名称冲突并且提供了自动内存管理。JavaScript确实有函数作用域,表示定义在函数中的参数和变量在函数外部是不可见的,而在一个函数内部任何位置定义的变量,在该函数内部任何地方都可见。

闭包

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

       // 和以对象字面量形式去初始化myObject不同,我们通过调用一个函数的形式去初始化myObject
        var myObject=(function(){
            var value=0
            return {
              increment:function(inc){
                value+=typeof inc ==='number'? inc :1
              },
              getValue:function(){
                return value
              }
            }
        })()
        // 我们并没有把一个函数赋值给myObject。我们是把调用该函数后返回的结果赋值给它
        var quo=function(status){
          return {
            get_status:function(){
              return status
            }
          }
        }
        // 构造一个quo实例
        var myQuo=quo('amazed')
        console.log(myQuo.get_status());
        // 这个quo函数被设计成无需在前加上new来使用,所以名字也没有首字母大写

循环中使用闭包

var add_the_handlers=function(nodes){
          var helper=function(i){
            return function(e){ // 闭包
              alert(i)
            }
          }
          var i;
          for(i=0;i<nodes.length;i++){
            nodes[i].onclick=helper(i)
          }
        }

模块

使用函数和闭包来构造模块。模块是一个提供接口却隐藏状态与实现的函数或对象。通过使用函数产生模块,我们可以完全摒弃全局变量的使用,从而缓解这个JavaScript的最为糟糕的特性带来的影响。

         String.method('deentityify',function () {
          // 字符实体表,映射字符实体的名字到对应的字符
          var entity={
            quot:'""',
            lt:'<',
            gt:'>'
          }
          // 返回deentityfy方法
          return function () {
            return this.replace(/&([^&;]+);/g,function (a,b) {
              var r=entity[b];
              return typeof r ==='string'?r:a
            })
          }
        }())
        console.log('&lt:quot:gt;'.deentityify()); // <''>
        // 注意:对象中保存字符实体的名字和他们对应的字符,我们把它放在一个闭包中
        // 注意最后使用()运算法立即调用刚刚构造处理的函数,调用后返回的函数才是deentityify方法

模块模式的一般形式是:

  • 一个定义了私有变量和函数的函数;利用闭包创建可以访问私有变量和函数的特权函数
  • 最后返回这个特权函数,或者把它们保存到一个可以访问到的地方 模块模式的作用:可以摒弃全局变量的使用,促进信息隐藏和其他优秀的设计实践。对于应用程序的封装,或者构造其他单例对象,模块模式非常有效。模块模式也可以用来产生安全的对象,如下:
var serial_maker=function(){
  var prefix='';
  var seq=0;
  return {
    set_prefix:function(p){
      prefix=String(p)
    },
    set_seq:function(s){
      seq=s;
    },
    gensym:function(){
      var result=prefix+seq;
      seq+=1;
      return result;
    }
  }
}
var seqer=serial_maker()
seqer.set_prefix('Q')
seqer.set_seq(1000)
var unique=seqer.gensym()// unique是'Q1000'

级联

级联就是对象调用方法可以采用链式调用,有一些方法没有返回值,如果让这些方法返回this而不是undefined,就可以启用级联。级联技术可以产生极富表现力的接口,它也能给那波构造"全能"接口的热潮降降温,一个接口没必要一次做太多的事情。

柯里化

柯里化允许我们把函数与传递给它的参数相结合,产生出一个新的函数。JavaScript并没有curry方法,但我们可以给Function.prototype扩展此功能:

Function.method('curry',function () {
  var slice=Array.prototype.slice;
  var args=slice.apply(arguments);
  var that=this;
  return function () {
    return that.apply(null,args.concat(slice.apply(arguments)))
  }
})

curry方法通过创建一个保存着原始函数和要被套用的参数的闭包来工作。它返回另一个函数,该函数被调用时,会返回调用原始函数的结果,并传递调用curry时的参数加上当前调用的参数。它使用Array的concat方法连接两个参数数组。

记忆

函数可以将先前的操作结果记录在某个对象里面,避免重复操作,这样的优化叫记忆。其实就是缓存数据,判断有缓存数据,有就直接用不用再做多余的计算操作。

  • 斐波那契数列(前面相邻两项之和等于后一项的值)
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)
      meno[n]=result
    }
    return result;
  }
  return fib
}()
// 在一个名为memo的数组里保存我们的存储结果,存储结果可以隐藏在闭包中,当函数被调用时,
// 这个函数首先检查结果是否已经存在,如果已经存在,就立即返回这个结果。

编写一个函数来构造带记忆功能的函数

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函数来定义fibonacci函数,提供memo和formula函数
var fibonacci=memoizer([0,1],function (recur,n) {
  return recur(n-1)+recur(n-2)
})