红宝书 第7章整理——函数+闭包

1、函数的两中定义方法

① 函数声明

语法:

function name (arg[0],arg[1],arg[2]....){

    //函数内容

}

关键点:

function关键字

函数名name!!!

特征:

函数声明提升——在执行代码之前会先读取每一个函数的声明,所以这种方法定义的函数,调用可以在定义之前


② 函数表达式(匿名函数式)

最常见的语法:

var name = function (arg[0],arg[1],arg[2]....) {

     //函数内容

}

理解:创建一个函数并把它赋值给变量name,但是注意function后面没有名称,name不是函数名

③ 二者区别

函数声明

会把函数名与函数体进行绑定,所以对这种情况来说,若代码中重复定义了这个函数,js会尝试修复这个错误,不同浏览器的理解方式会有不同,所以尽量不要这样做。

此外就是之前提到的,会在代码执行前预先读函数声明。

函数表达式

相当于把函数当成值来用,所以var定义的变量重名也无所谓。

2、闭包

① 闭包定义

官方定义:有权访问另一个函数作用域中的变量的函数

常见的创建方式:在一个函数内部创建另一个函数。

注意,创建的新函数可以用函数声明,也可以用匿名函数,只要创建了就是个闭包。

理解

function A () {
    var a  =0;   //定义A的变量
    
     function B(){
       alert (a);      // B算是A的局部,所以可以调用A 的局部变量
}
    
}


alert(a);     //错误! a算是function A 的局部变量,外部访问不到的

在function A 内部定义了function B ,所以functionB 是一个闭包。闭包functionB 有权访问函数A 的所有变量。

② 闭包的使用结构

正因为闭包有可以访问一个函数内部的局部变量,那么我们可以利用这点,使用闭包,来在函数外部获取内部的值。

此处先引用阮一峰老师的闭包博文,引一下内容:

————————————————————————————————————————————

一、变量的作用域

要理解闭包,首先必须理解Javascript特殊的变量作用域。

变量的作用域无非就是两种:全局变量和局部变量。

Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量。

      var n=999;

      function f1(){
        alert(n);
      }

      f1(); // 999


另一方面,在函数外部自然无法读取函数内的局部变量。

      function f1(){
        var n=999;
      }

      alert(n); // error


这里有一个地方需要注意,函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量!

      function f1(){
        n=999;
      }

      f1();

      alert(n); // 999


二、如何从外部读取局部变量?

出于种种原因,我们有时候需要得到函数内的局部变量。但是,前面已经说过了,正常情况下,这是办不到的,只有通过变通方法才能实现。

那就是在函数的内部,再定义一个函数。

      function f1(){

        var n=999;

        function f2(){
          alert(n); // 999
        }

      }


在上面的代码中,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。这就是Javascript语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。

既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗!

      function f1(){

        var n=999;

        function f2(){
          alert(n);
        }

        return f2;

      }

      var result=f1();

      result(); // 999


————————————————————————————————————————————

所以,使用闭包的精髓在于

闭包中引用局部变量 + return

返回这个闭包,在外部进行接收,那么在这里我们一般使用var一个变量进行接收,那么实际上就是

   var a = funtion();     //这个地方实际是一个匿名函数的定义式,闭包与匿名函数这两个概念在这个地方才会相交。

再进行下一步操作  a();

③ 使用闭包时,两处易错的地方——循环+this

1、循环中:

function A (){
  var result = new Array();
 
   for(var i = 0; i<10 ; i++){
     result[i] = function(){    //相当于用循环写了10个闭包函数,每个函数返回自己的索引值
          return i;
};
}
   return result;       //返回这10个闭包函数
}

var a = A();
a();      //10,10,10,10,10,10.....

然而实际上,返回的内容全是10

原因:每个闭包函数的作用域链中都保存着function A 的变量result,所以对它们来说引用的是同一个i,当return时,i已经累加到10,所以返回是全都是10

自己的理解

就相当于 var a = A();  此时a接受A中的闭包,就相当于已经将function A 运行了一遍,只除了闭包的内容,

就是除了function(){

   return i;

};

其他像之前的for循环已经完事了,i已经加到10了

a中保存的状态是运行一遍之后的初始化状态,实际上就是有10个闭包函数待执行

然后再a();   此时就直接执行10个闭包函数内容:

   function(){

      return i;

}

那么就是执行10次这个函数,返回10 个10

2、this:

在闭包中使用this时也容易出错,因为this其实是一个指代,指代当前真正运行这个函数时的环境。

那么就同上一个循环中会有的问题,闭包函数真正把它打开封装开始运行是在外面调用时,所以非常有可能此时this时全局变量了。

上一个例子:

var name = "window";

var object = {
   name: "my",
   getname : function(){
    return function(){
       return this.name;
};
}
};

alert(object.getname()());     //"window"

为什么闭包getname没有正确调用对象中的name,而是调用了全局变量中的window?

因为在外部才开始打开闭包,执行return this.name;   而此时this已经变成了全局变量。

若变成 return object.name,那么就会是“my”了。

如果非要使用this,那么可以将this变成另一个变量,然后在闭包中调用

var name = "window";

var object = {
   name: "my",
   getname : function(){
     
      var that = this;    //将this也变成一个变量

    return function(){
       return that.name;    //在闭包中调用这个变量,这样就相当于将this与这个函数建立了联系,不会随着外部调用而丢失
};
}
};

alert(object.getname()());     //"window"


④ 闭包的用处


1、常见的用处

     1、读取函数内部变量+内部变量的值始终保存

function f1(){

    var n=999;

    nAdd=function(){n+=1}    //相当于用匿名函数的方法也创建了一个闭包函数,并赋值给了一个全局变量
                                 //为啥此处用了一个全局变量写函数?因为要是nAdd也用return,那么f1就有两个返回函数了,外部没法调用了
   
  function f2(){             //此处相当于用函数声明写了一个闭包,并返回
      alert(n);
    }

    return f2;

  }

  var result=f1();

  result(); // 999

  nAdd();

  result(); // 1000

在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。

为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

这段代码中另一个值得注意的地方,就是"nAdd=function(){n+=1}"这一行,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。


     2、红宝书里的两个高级用法:

      ① 模仿块级作用域(私有作用域)

     之前说过在js中没有块级作用域,就是如下:

function A (){
  for(var i =0 ; i<10: i++)
   {
      alert(i);     //这句感觉像是一个块级作用域,i为for私有的,但是js无块级作用域,花括号相当于没有
}
   alert(i);     //这句不会报错,前面的花括号相当于没有
}
    即使你在 function A中又声明了多个i,js不会报错,但是会自动忽略后面所有的i声明,只会进行i操作。

  步骤:创建并立即调用这个函数,这样既可以执行其中的代码,又不会在内存中留下对该函数的引用

格式:

(function () {
   //块级作用域
}) ();

解释:

将function套在一个括号里,正常如果匿名方式创建一个函数需要var一个变量来对function进行保存,但是这不用保存,马上跟着下一对括号就代表着执行。所以没有变量来对function进行接收保存,执行完就会全部被销毁。

有种那叫什么,一直飞的鸟,不能落地?落地就死了的赶脚......

用处:

避免有过多的全局变量,通过这种方式创建完就销毁,可以在全局环境中任意添加函数了

可以减少闭包占用内存的问题。

       ② 私有变量

任何在函数中定义的变量,都可以理解为私有变量,那么对于构造函数的方法来创建对象来说,可以利用私有成员和特权成员来隐藏对象内部的数据。

举例:

function Person(name) {
   this.getname = function () {
     return this.name;
};
   this.setname = function (value) {
    name = value;
};
}

var person = new Person("jack");
alert(person.getname());     //"jack"
person.setname ("tom");
alert(person.getname());     //"tom"
在Person对象中,定义了两个特权方法,实际上就是在构造函数中写了两个闭包,可以访问到对象中的变量name。

那么在实例化后,只有通过getname函数和setname函数才可以访问到name参数,相当于保护了name

缺点:这种方式是通过构造函数中实现的对象方法,缺点就是与构造函数的缺点,每一次实例方法都会被重建一次。

改进

静态私有变量

通过在私有作用域中定义变量或者函数

还有一种模块模式

3、闭包与匿名函数关系

 其实没什么关系。闭包就是函数中定义的函数,具体怎么定义的都可以,可以是函数声明式,也可以是匿名函数式。

但是在利用闭包,通过return闭包函数来传递内部参数时,一般就会用匿名函数式进行接收这个函数包。

4、递归

递归就是在一个函数中通过函数名,自己调用自己。

最经典的阶乘函数:

function A (num) {
  if(num<=1){
     return 1;
}
   else{
     return num * A(num-1);
}
}

但是这种方法其实有缺点,就是与函数的名称A 有很大的耦合关系,当函数名称改变时,例如:

var B = A;
A = null;
B(5);     //ERROR!!

当把A函数传递给B时,然后将A制空,这时调用B 会报错。

改进:

利用arguments.callee来代替函数名。(之前在讲函数时说到过)

function A (num) {
  if(num<=1){
     return 1;
}
   else{
     return num * arguements.callee(num-1);
}
}

降低了函数与函数名称的耦合度,更保险。


你可能感兴趣的:(Java,Script,JavaScript语言基础)