Better

Ethan的博客,欢迎访问交流

JavaScript立即执行函数的思考

工作中发现立即执行函数还能这么写:var a=function(){console.log(123)}(),OMG,还能这么玩,感觉对这一块的理解还需要加强,因此补习了这一块的知识。

函数调用

函数调用通常有如下两种方式:

//函数声明语句写法
function test(){};
test();

//函数表达式写法
var test = function(){};
test();

但有时需要在定义函数之后,立即调用该函数。这种函数就叫做立即执行函数,全称为立即调用的函数表达式IIFE(Imdiately Invoked Function Expression)

JavaScript引擎规定,如果function关键字出现在行首,一律解释成函数声明语句

错误写法

  • 函数声明语句需要一个函数名,由于没有函数名,所以报错
    //SyntaxError: Unexpected token (
    function(){}();
    
  • 函数声明语句后面加上一对圆括号,只是函数声明语句与分组操作符的组合而已。由于分组操作符不能为空,所以报错
    //SyntaxError: Unexpected token )
    function foo(){}();
    //等价于
    function foo(){};
    ();//SyntaxError: Unexpected token )
    
  • 函数声明语句加上一对有值的圆括号,也仅仅是函数声明语句与不报错的分组操作符的组合而已
    function foo(){}(1);
    //等价于
    function foo(){};
    (1);
    

常用写法

解决方法就是不要让function出现在行首,让引擎将其理解成一个表达式。

  • 最常用
    (function(){ /* code */ }()); 
    (function(){ /* code */ })();
    
    写法区别:
    (function(){})(); 是 把函数当作表达式解析,然后执行解析后的函数 相当于 var a = function(){}; a(); a得到的是函数
    (function(){}()); 是把函数表达式和执行当作语句直接执行、 相当于 var a = function(){}(); a得到的是结果
  • 其他写法
    var i = function(){ return 10; }();
    !function(){ /* code */ }();
    void function(){ /* code */ }();
    // 等同于(function (){})
    

传统写法

function foo() {...}
foo();
  • 为什么用IIFE
    • 传统的方法啰嗦,定义和执行分开写;
    • 惰性载入,直接上代码,比如浏览器事件绑定
      var addEvent = (function () { 
      if (document.addEventListener) { 
      return function (type, element, fun) { 
      element.addEventListener(type, fun, false); 
      } 
      } else if (document.attachEvent) { 
      return function (type, element, fun) { 
      element.attachEvent('on' + type, fun); 
      } 
      } else { 
      return function (type, element, fun) { 
      element['on' + type] = fun; 
      } 
      } 
      })();
      
    • 传统的方法直接污染全局命名空间,自调用匿名函数就用来防止变量弥散到全局(包括函数本身);
    • 隔离作用域,用这种方式return接口会很安全。以免各种js库冲突。基本代码如下:
      var add = (function(){
      var counter = 0;
      return function(){
          return ++counter; 
      }
      })();
      
    • 如果需要全局对象呢?这里很奇怪,我不传入在匿名函数内部同样可以访问window对象哇。
      void function (global) {
      // 在这里,global 就是全局对象了
      }(this)  // 在浏览器里,this 就是 window 对象
      
      那么为什么要传入呢?在网上查到一个非常典型的例子,代码如下:
      (function (window, $, undefined) {
      play=function(){
          $("#demo").val("This is a demo.");
      }
      window.wbLogin = play;
      })(window, jQuery);
      
      1. 为什么传入window?
        通过传入 window 变量,使得 window 由全局变量变为局部变量,当在代码块中访问 window 时,不需要将作用域链回退到顶层作用域,这样可以更快的访问 window;同时将 window 作为参数传入,可以在压缩代码时进行优化。
      2. 为什么传入jQuery?
        jQuery传入后将参数写成$可以保证在此函数内$为jQuery而不是其他类似使用$符号的库。
      3. 为什么传入undefined?
        在自调用匿名函数的作用域内,确保 undefined 是真的未定义。因为 undefined 能够被重写,赋予新的值。
        由于没有传入第三个参数,自然就是undefined。由于JavaScript中undefined是一个变量,可以被改变,所以这样可以保证undefined判断时的准确性。有时判断时使用typeof xxx === 'undefined'也是因为这个原因。
  • 为什么如下写是错的?
    function foo(...){}();
    
    因为 function foo(...){} 这个部分只是一个声明,对于解释器来说,就好像你写了一个字符串 "function foo(...){}",它需要使用解析函数,比如 eval() 来执行它才可以。所以把 () 直接放在声明后面是不会执行,这是错误的语法。

用途

IIFE一般用于构造私有变量,避免全局空间污染,隔离作用域目的。
假设有一个需求,每次调用函数,都返回加1的一个数字(数字初始值为0) 。

  1. 全局变量,但变量a实际上只和add函数相关,却声明为全局变量,不太合适。

    var a = 0;
    function add(){
     return ++a;
    }
    console.log(add());//1
    console.log(add());//2
    
  2. 自定义属性,用户有可能在不知情的情况下重置add.count

    function add(){
     return ++add.count;
    }
    add.count = 0;
    console.log(add());//1
    console.log(add());//2
    
  3. IIFE

    var add = (function(){
     var counter = 0;
     return function(){
         return ++counter; 
     }
    })();
    console.log(add())//1
    console.log(add())//2
    

总结

看上去一个很小的知识点,在查找资料学习的过程中,可以延伸出许多的知识哇,骚年,好好努力吧。

资料



留言