• JavaScript进阶(二十三):立即执行函数(匿名函数)( ( ) { } ( ) )含义解析


    一、前言

    前端项目改造过程中,引入的工具类实现如下:

    var tensquared=(function(x) {
        return x*x;
    }(10));  
    
    • 1
    • 2
    • 3

    拆解以上语句如下:

    1. var tensquared = xx; 这是赋值语句;

    2. function (x){ return x*x; } 这是一个匿名函数;所谓的匿名函数是指它没有自己的名字,既不是function name(){};拥有一个函数名,也不是var name = function(){};将函数赋给一个变量地址用于日后的调用;

    为什么会有这样的函数呢,因为有时候只是临时地一次性地使用一个函数,用过了这个函数也就再没用了,那么这个时候就用到了匿名函数(当然,并不一定说只有一次性的才会用到匿名函数,还有其它的情况,不一一举例了。)

    1. 函数() 这样的语句我们都知道是让一个函数运行,而括号中的值表示传值,比如:
    function name(arg){ alert(arg); };
    name('yes');
    
    • 1
    • 2

    那么,程序执行name();这个语句的时候,会将’yes’这个字符串传递到函数name中去,并执行name函数;

    上面的函数我们再来理解一下:
    function(x){return x*x;}是一个函数,然后直接在它后面加上个括号,是不是表示直接运行这个匿名函数呢:

    function(x) {return x*x;}(10)
    
    • 1

    这一步等价于下面的方法:

    function name(x){return x*x;}
    name(10);
    
    • 1
    • 2

    再加上赋值:

    var tensquared = function(x) {return x*x;}(10);
    
    • 1

    是不是与下面这一系列的代码起到的效果一样呢:

    function name(x){return x*x}
    var tensquared = name(x);
    
    • 1
    • 2

    应用下面这种实现方式之后,name()函数以后还可以通过name(值);这样的方式再一次去调用,因为有函数名,就可以用这个方法调用到它;

    var tensquared=function(x) {return x*x;}(10)这样的方式,是当时就执行了,然后将执行的结果赋值给了它前面的变量tensquared,以后想再调用return x*x所在的函数,却没办法了,因为它没名字,运行后就找不到了;

    最后说说外面的括号,这个括号其实是可有可无的,而且这个括号的用法与人们常用的另一种用法有所偏差,因为括号括在最外围,已经失去了它的意义,其实应该是这样的:

    var tensquared=(function(x) {return x*x;})(10);
    
    • 1

    只将匿名函数本体给括了起来,因为有时候function函数体很长,那么加一个括号告诉程序,这是一个完整的整体,其实这一步就算这样用,也是可有可无的。

    再说说这种用法的意义:

    像这种用法,大多数时候就是用来获取到一个值,而这个值却是需要一系列的计算后才能得到的,而这个计算的过程,只需用到一次,不需要再用第二次了,这时候,这种语句结构就有用了。

    例如获取用户在使用浏览器类型:

    var browser = function(){
        if(IE浏览器) return 'IE';
        else if(是chrome浏览器) return 'CH';
        else if(是Firefox浏览器) return 'FF';
    }();
    
    • 1
    • 2
    • 3
    • 4
    • 5

    后面如果再想知道用户的浏览器名称,只要调用browser这个变量,看看返回的字符串就可以了。

    二、立即执行函数

    阅读源码过程中,经常看到一些大牛的JS源码会在function前面加语句结束符;

    ;(function(arg){
    	//some js code in here
    })(param);
    
    • 1
    • 2
    • 3

    ;function($,undefined) 是什么用处 ?

    其实它就是创建了一个匿名函数function(arg){ //some js code in here },然后再执行且只执行该函数一次,param为实参。

    最前面加;是为了防止其他语句的影响,因此语句结束符;可有可无。如:

    new
    (function() {
        this.prop = 4;
    }) ().prop;
    
    • 1
    • 2
    • 3
    • 4

    上面将匿名函数用作构造函数,然后实例化并取出prop属性值。

    在前面加分号可以有以下几种用途:

    1. 防止多文件集成成一个文件后,高压缩出现语法错误。

    2. 这是一个匿名函数,一般js库都采用这种自执行的匿名函数来保护内部变量 (function(){})()

    3. 因为undefinedwindow的属性,声明为局部变量之后,在函数中如果再有变量与undefined作比较的话,程序就可以不用搜索undefinedwindow,可以提高程序性能。

    立即执行函数👉🏻:声明一个函数,并马上调用这个匿名函数就叫做立即执行函数;即立即执行函数是定义函数以后立即执行该函数。

    定义好函数之后,立即调用该函数,这时不能在函数的定义后面直接加圆括号,会产生语法错误。

    //这是错误的
    function fn(){}()
    
    • 1
    • 2

    这是因为:function 这个关键字,既可以当做语句,也可以当做表达式
    比如:

    //语句
    function fn() {};
    //表达式
    var fn = function (){};
    
    • 1
    • 2
    • 3
    • 4

    为了避免解析上的歧义,JS引擎规定,如果function出现在行首,一律解析成语句。

    因此JS引擎看到行首是function关键字以后,认为这一段都是函数定义,不应该以原括号结尾,所以就报错了。

    解决方法就是不要让function出现在行首,让JS引擎将其理解为一个表达式。最简单的处理就是将其放在一个圆括号里,比如下边:

    //立即执行函数的两种写法
    
    //第一种:用括号把整个函数定义和调用包裹起来
    (function(){
     //function body
    }())
    
    //第二种:用括号把函数定义包裹起来,后面再加括号
    (function (){
     //function body
    })()
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    上边的两种写法,就是立即执行函数的两种写法,都是以圆括号开头,JS引擎会认为后面跟的是表达式,而不是一个函数定义语句,所以就避免了错误,这就叫做"立即调用的函数表达式"

    在这里插入图片描述

    立即执行函数一般也写成匿名函数,匿名函数写法为function(){/…/},所谓匿名函数,就是使用function关键字声明一个函数,但未给函数命名,倘若需要传值,直接将参数写到括号内即可。

    将它赋予一个变量则创建函数表达式,赋予一个事件则成为事件处理程序等。但是需要注意的是匿名函数不能单独使用,否则会js语法报错,至少要用()包裹起来。

    了解了立即函数的原理,就可以再扩展出其他的写法:

    (function foo(){console.log("Hello World!")}())//用括号把整个表达式包起来,正常执行
    (function foo(){console.log("Hello World!")})()//用括号把函数包起来,正常执行
    !function foo(){console.log("Hello World!")}()//使用!,求反,这里只想通过语法检查。
    +function foo(){console.log("Hello World!")}()//使用+,正常执行
    -function foo(){console.log("Hello World!")}()//使用-,正常执行
    ~function foo(){console.log("Hello World!")}()//使用~,正常执行
    void function foo(){console.log("Hello World!")}()//使用void,正常执行
    new function foo(){console.log("Hello World!")}()//使用new,正常执行
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    立即执行函数主要有以下特点:

    1. 不必为函数命名,避免污染全局变量。
    2. 立即执行函数内部形成一个独立的作用域,可以封装一些外部无法读取的私有变量,这个作用域里面的变量,外面访问不到,这样就可以避免变量污染。
    3. 封装变量。
    4. 闭包和私有数据。

    2.1 立即执行函数使用的场景

    1. 代码逻辑在页面加载完成之后,不得不执行一些设置工作,比如时间处理器,创建对象等等。
    2. 所有的这些工作只需要执行一次,比如只需要显示一个时间。
    3. 但是这些代码也需要一些临时的变量,但是初始化过程结束之后,就再也不会被用到,如果将这些变量作为全局变量,不是一个好的设计,可以用立即执行函数——去将所有的代码包裹在它的局部作用域中,不会让任何变量泄露成全局变量,如下代码:
    当前时间:<span id="today"></span>
    <script>
    	(function(){
    		var todaydom=document.getElementById("today");
    		var days=["星期日","星期一","星期二","星期三","星期四","星期五","星期六"1; 
    		var today=new Date( );
    		var year=today.getFullYear(); 
    		var month=today.getMonth()+1; 
    		var date=today.getDate(); 
    		var day=today.getDay();
    		var msg=year+"年"+month+"月"+date+"日"+days [day]; 
    		todaydom.innerHTML=msg;
    	})()
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    比如上面的代码,如果没有被包裹在立即执行函数中,那么临时变量todaydom,days,today,year,month,date,day,msg都将成为全局变量(初始化代码遗留的产物)。用立即执行函数之后,这些变量都不会在全局变量中存在,以后也不会其他地方使用,有效的避免了污染全局变量。

    总结👇🏻:立即执行函数会形成一个单独的作用域,可以封装一些临时变量或者局部变量,避免污染全局变量。

    像其他函数一样,立即执行函数也可以有返回值。除了可以返回基本类型值以外,立即执行函数也能返回任何类型的值,比如对象,函数。

    var result = (function(){
    	var res = 2+2;
    	return function( ) { return res;
    	})()
    console.log(result());//4
    
    • 1
    • 2
    • 3
    • 4
    • 5

    上例中立即执行函数的返回值被赋值给了一个变量result,这个函数简单的返回了res的值,这个值事先被计算并被存储在立即执行行数的闭包中。

    2.2 立即执行函数有哪些作用?

    2.2.1 改变变量的作用域(创建一个独立的作用域)
    <body>
        <ul id="list">
            <li>公司简介</li>
            <li>联系我们</li>
            <li>营销网络</li>
        </ul>
        <script>
          var list = document.getElementById("list");
          var li = list.children;
          for(var i = 0 ;i<li.length;i++){
           (function(j){
                li[j].onclick = function(){
                  alert(j);
              })(i); 把实参i赋值给形参j
            }
          }
         </script>  
    </body>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    改变变量i的作用域,把全局变量i以参数的形式传递到立即执行函数中,在立即执行函数中定义变量i的形参变量j,变量j就是在立即执行函数的作用域中。(给每个li创建了一个作用域块,点击的时候寻找自由变量j,在立即执行块中找到)

    2.2.2 封装临时变量
    当前时间:<span id="today"></span>
    <script>
    	(function(){
    		var todaydom = document.getElementById("today");
    		var days = ["星期日","星期一","星期二","星期三","星期四","星期五","星期六"1; 
    		var today = new Date( );
    		var year = today.getFullYear(); 
    		var month = today.getMonth()+1; 
    		var date = today.getDate(); 
    		var day = today.getDay();
    		var msg = year+"年"+month+"月"+date+"日"+days [day]; 
    		todaydom.innerHTML=msg;
    	})()
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在上面的代码中,可以封装临时变量,避免全局变量的污染。也可以返回一个在全局中需要的变量(用return实现)。

    三、拓展阅读

  • 相关阅读:
    docker小技能:部署mysql
    flink 自定义序列化对象Sink/Source
    el-select与el-tree的联动,切换勾选保存回显功能(全部代码)
    开源免费的流程图软件draw.io
    Next.js多页布局getLayout使用方法
    Flink CDC MySQL同步MySQL错误记录
    神经网络模型数据处理,人工神经网络预测模型
    WPF中 INotifyPropertyChanged
    【.Net实用方法总结】 整理并总结System.IO中TextReader类及其方法介绍
    Proteus 新建工程
  • 原文地址:https://blog.csdn.net/sunhuaqiang1/article/details/133701819