深入理解 函数、匿名函数、自执行函数

基础概念:定义函数的方式

  一般定义函数有两种方式:
    1:函数的声明
    2:函数表达式

  函数的声明

      如下代码就是函数声明的代码结构:

     function sum(x,y){
         alert(x+y);
     }
     sum(1,2); //3

      关于函数声明,它最重要的一个特征就是函数声明提升,意思是执行代码之前先读取函数声明。这意味着可以把函数声明放在调用它的语句之后。如下代码可以正确执行:

     sum(1,2); //3
     function sum(x,y){
         alert(x+y);
     }

  函数表达式

     函数表达式中有几种不同的语法。最常见和最具代表性的一种如下代码所示:

     var ss = function(x,y){
         alert(x+y);
     };
     ss(1,2);

 

     这种形式看起来好像是常规的变量赋值语句。但是函数表达式和函数声明的区别在于,函数表达式在使用前必须先赋值。所以一下代码执行的时候就会出错:

     ss(1,2); //报错,显示undefined is not a function
     var ss = function(x,y){
         alert(x+y);
     };

     造成这种现象是因为解析器在向执行环境中加载数据时,解析器会率先读取函数声明,并使其在执行任何代码前可用;至于函数表达式,则必须等到解析器执行到它的所在的的代码行,才会真正的被解析。

     函数表达式中,创建的函数叫做匿名函数,因为function关键字后面没有标识符。

匿名函数的调用方式

     匿名函数,顾名思义就是没有名字的函数。上面的函数表达式中的创建,即创建一个匿名函数,并将匿名函数赋值给变量ss,用ss来进行函数的调用,调用的方式就是在变量ss后面加上一对括号(),如果有参数传入的话就是ss(1,2),这就是匿名函数的一种调用方式。

还有一种匿名函数的调用方式是:使用()将匿名函数括起来,然后后面再加一对小括号(包含参数列表)。我们再看一下以下一个例子:

alert((function(x,y){return x+y;})(2,3));//5
alert(( new Function("x","y","return x+y;"))(2,3));//5

    在javascript中,是没有块级作用域这种说法的,以上代码的这种方式就是模仿了块级作用域(通常成为私有作用域),语法如下所示:

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

    以上代码定义并立即调用了一个匿名函数。经函数声明包含在一对圆括号中,表示它实际上是一个函数表达式。而紧随其后的另一对圆括号会立即调用这个函数。

    然而要注意一点:

     function(){
         
     }();

    上面的代码是错误的,因为Javascript将function关键字当作一个函数声明的开始,而函数声明后面不能加圆括号,如果你不显示告诉编译器,它会默认生成一个缺少名字的function,并且抛出一个语法错误,因为function声明需要一个名字。有趣的是,即便你为上面那个错误的代码加上一个名字,他也会提示语法错误,只不过和上面的原因不一样。在一个表达式后面加上括号(),该表达式会立即执行,但是在一个语句后面加上括号(),是完全不一样的意思,他的只是分组操作符(此处摘自汤姆大叔的博客)。

// 下面这个function在语法上是没问题的,但是依然只是一个语句
// 加上括号()以后依然会报错,因为分组操作符需要包含表达式
 
function foo(){ /* code */ }(); // SyntaxError: Unexpected token )
 
// 但是如果你在括弧()里传入一个表达式,将不会有异常抛出
// 但是foo函数依然不会执行
function foo(){ /* code */ }( 1 );
 
// 因为它完全等价于下面这个代码,一个function声明后面,又声明了一个毫无关系的表达式: 
function foo(){ /* code */ }
 
( 1 );

 

    所以上面代码要是想要实现,就必须要实现赋值,如a = function(){}(),"a="这个告诉了编译器这个是一个函数表达式,而不是函数的声明。因为函数表达式后面可以跟圆括号。所以下面两段代码是等价的。

        var aa = function(x){
            alert(x);
        }(5);//5
(function(x){alert(x);})(5);//5

 

  有上面对于函数和匿名函数的了解,我们引申出来了一个概念,即自执行函数,让我们更加深入的了解为什么。a = function(){}()这个表示可以让编译器认为这个是一个函数表达式而不是一个函数的声明。

自执行函数

    我们创建了一个匿名的函数,并立即执行它,由于外部无法引用它内部的变量,因此在执行完后很快就会被释放,关键是这种机制不会污染全局对象。
    自执行函数,即定义和调用合为一体。下面我们来看下一下自执行函数的一些表达方式,下面一些专业的讲法摘自汤姆大叔的博客

// 下面2个括弧()都会立即执行
 
(function () { /* code */ } ()); // 推荐使用这个
(function () { /* code */ })(); // 但是这个也是可以用的
 
// 由于括弧()和JS的&&,异或,逗号等操作符是在函数表达式和函数声明上消除歧义的
// 所以一旦解析器知道其中一个已经是表达式了,其它的也都默认为表达式了
// 不过,请注意下一章节的内容解释
 
var i = function () { return 10; } ();
true && function () { /* code */ } ();
0, function () { /* code */ } ();
 
// 如果你不在意返回值,或者不怕难以阅读
// 你甚至可以在function前面加一元操作符号
 
!function () { /* code */ } ();
~function () { /* code */ } ();
-function () { /* code */ } ();
+function () { /* code */ } ();
 
// 还有一个情况,使用new关键字,也可以用,但我不确定它的效率
// http://twitter.com/kuvos/status/18209252090847232
 
new function () { /* code */ }
new function () { /* code */ } () // 如果需要传递参数,只需要加上括弧()

 

     上面所说的括弧是消除歧义的,其实压根就没必要,因为括弧本来内部本来期望的就是函数表达式,但是我们依然用它,主要是为了方便开发人员阅读,当你让这些已经自动执行的表达式赋值给一个变量的时候,我们看到开头有括弧(,很快就能明白,而不需要将代码拉到最后看看到底有没有加括弧。

 

    即要是想要这样function(){}()来实现自执行,可以用一些操作符在function的前面来消除歧义。

function(x){
    alert(x);
}(5);//报错,function name expected
 
var aa = function(x){
    alert(x);
}(1);//1
 
true && function(x){
    alert(x);
}(2);//2
 
0, function(x){
    alert(x);
}(3);//3
 
!function(x){
    alert(x);
}(4);//4
 
~function(x){
    alert(x);
}(5);//5
 
-function(x){
    alert(x);
}(6);//6
 
+function(x){
    alert(x);
}(7);//7
 
 new function (){
     alert(8);//8
 }
 
  new function (x){
    alert(x);
}(9);//9

     很多情况下,可以利用自执行函数和闭包来保存某个特殊状态中的值,具体想看下方讲解。

闭包

    关于闭包,在我的上一篇文章中,做出了基础的了解: 杂七杂八JS : 浅谈闭包一 

    由于作用域链的配置机制,使得闭包只能取得包含函数中任何变量的最后一个值。即说明了闭包中所保存的是整个变量对象,而不是某一个特殊的变量。我们 用下面这个例子来说明这个问题。

   例子一:

     function createFunction(){
         var result = new Array();
         for( var i = 0; i <10; i++){
             result[i] = function(){
                 return i;
             };
         }
         return result;
     }
     var aa = createFunction();
     alert(aa[0]());//10
     alert(aa[1]());//10

       在这个函数中,我们直接将闭包赋值给数组。这个函数会返回一个函数数组。表面上来看,似乎每个函数都应该返回自己的索引,即位置0的函数返回0,位置1的函数返回1一次类推。但实际上,如同上面例子,每个函数都返回了10。因为每个函数的作用域链中都保存createFunctions()函数的活动对象,所以它们引用的都是同一个变量i。当createFunctions()函数返回后,变量i的值死10,此时每个函数都引用着保存变量i的同一个变量对象。所以在每个函数内部i的值都是10。‘

    所以,我们可以通过如下例子,创建一个自执行函数(匿名函数)强制让闭包的行为符合预期。

    例子二:

     function createFunction1(){
         var result = new Array();
         for( var i = 0; i <10; i++){
             result[i] = function(num){
                 return function(){
                     return num;
                 };
             }(i);
         }
         return result;
     }
 
     var bb = createFunction1();
     alert(bb[0]());//0
     alert(bb[1]());//1

    从createFunctions1()这个函数的执行结果来看,每个函数都返回各自不同的索引值了,是什么原因呢?
    在createFunctions1()这个函数中,我们没有直接将闭包赋值给数组,而是定义了一个匿名函数,并将立即执行该匿名函数的结果赋值给数组。对于立即执行的匿名函数来说,由于外部无法引用它内部的变量,因此在执行完后很快就会被释放。所以这里的匿名函数有一个参数num,也就是最终的函数要返回的值。在调用每个匿名函数时,我们传入了变量i。由于函数是按值传递的,所以会将变量i的当前值赋值给参数num,而这个匿名函数内部,又创建并返回了一个返回num的闭包。这样一来,result数组中的每个函数都有自己num的一个副本,因此就可以返回各自不同的数值了。

 

     在看过各个网址和查阅书籍,终于将这些概念性的信息理清晰一些了。
     参考网址:
         http://www.cnblogs.com/TomXu/archive/2011/12/31/2289423.html
         http://www.cnblogs.com/mzwr1982/archive/2012/05/20/2509295.html
         http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html
         http://blog.csdn.net/natineprince/article/details/4759533
     参考书籍:
          《javascript 高级程序设计》

 转发自:https://blog.csdn.net/xixiruyiruyi/article/details/54894404

posted @ 2018-07-09 11:48  小白点  阅读(10196)  评论(0编辑  收藏  举报