zoukankan      html  css  js  c++  java
  • JavaScript中的匿名函数、立即执行函数和闭包

    匿名函数是没有函数名的,不能单独使用;

    立即执行函数是基于匿名函数实现的,也没有函数名,会在定义后立即执行;

    闭包是有权访问另一个函数作用域中的变量的函数。匿名函数、立即执行函数只要满足 有权访问另一个函数作用域中的变量 这一个条件,就成了闭包。

    匿名函数

    匿名函数:没有函数名的函数

    匿名函数不能单独定义与使用

    function foo() {
      console.log('普通函数');
    }
    // 去掉函数名 foo 
    function () { // SyntaxError: Function statements require a function name
      console.log('匿名函数');
    }
    
    匿名函数的应用场景
    • 用于函数表达式
    • 作为返回值
    • 用于定义对象方法
    • 作为回调函数
    • 用于立即执行函数
    • 用于DOM元素注册事件
    • 其他 ...

    用于函数表达式

    var sum = function (num1, num2) {
      return num1 + num2;
    };
    console.log(sum(2, 3)); 
    

    作为返回值

    function sum(sum1, sum2) { 
    
      return function() {
        return sum1 + sum2;    
      }
    
    }
    console.log(sum(2, 3));    // [Function]
    console.log(sum(2, 3)());  // 5
    

    用于定义对象方法

    var obj = {
      name: 'uakora',
      age: 27,
      foo: function() {
        console.log(this.name + ' ' + this.age);
      }
    };
    obj.foo(); // uakora 27 
    

    作为回调函数

    setTimeout(function() {
      console.log('匿名函数作为回调函数');
    }, 1000);
    

    用于立即执行函数

    (function() {
      console.log('立即执行函数是基于匿名函数创建的');
    }());
    

    用于DOM元素注册事件

    <input type="button" value="Click me!" id="btn">
    <script>
        var btn = document.querySelector("#btn");
        //给按钮注册点击事件
        sub.onclick = function(){
            console.log('Click event');
        }
    </script>
    

    立即执行函数

    立即执行函数(IIFE,Immediately-Invoked Function Expression),是一种在定义后就会立即执行的函数,其实质是一种语法。

    立即执行函数的形式

    两种常用形式

    常用形式一:将匿名函数包裹在一个括号运算符中,后面再跟一个括号

    (function () {
      console.log('立即执行函数');
    })(); // !!!特别说明:若此立即执行函数后面立马又跟着一个立即执行函数,一定要在结尾加分号,否则后面的立即执行函数会报错!
    
    // 上一个立即执行函数不加分号,下行代码将报错:TypeError: (intermediate value)(...) is not a function
    (function (a, b, c) { // 形参
      console.log(a + b + c);  // 6
    })(1, 2, 3); // 实参
    

    常用形式二:匿名函数后面跟一个括号,再将整个包裹在一个括号运算符中

    (function () {
      console.log('立即执行函数');
    }());
    
    (function (a, b, c) { // 形参
      console.log(a + b + c);  // 6
    }(1, 2, 3)); // 实参
    

    其他形式

    可以用 !、+、-、~ 运算符代替常用形式一中的第一个括号

    !function (a, b, c) { 
      console.log(a + b + c);  // 6
    }(1, 2, 3);
    
    +function (a, b, c) { 
      console.log(a + b + c);  // 6
    }(1, 2, 3);
    
    -function (a, b, c) { 
      console.log(a + b + c);  // 6
    }(1, 2, 3);
    
    ~function (a, b, c) { 
      console.log(a + b + c);  // 6
    }(1, 2, 3);
    
    立即执行函数的作用

    立即执行函数最本质的作用是:创建一个独立的作用域。利用这一功能,可以

    • 初始化数据和页面(只执行一次)
    • 模块化开发中,定义私有变量,防止污染全局(独立作用域)
    • 解决闭包中的状态保存问题;(常见的一个函数内部返回多个函数,调用这些函数,打印父函数内部变量的问题)

    案例分析

    var liList = document.getElementsByTagName('li');
    for(var i = 0; i < liList.length; i++) {
      liList[i].onclick = function() {
        alert(i); // 结果:alert 出来的总是 6, 而不是0、1、2、3、4、5
      }
    }
    

    期望结果:0、1、2、3、4、5
    实际结果:alert 出来的总是 6
    原因分析:没有形成独立作用域,每次点击执行函数都是去访问同一个 i 值(且是循环结束后的值 6)
    解决方案:使用立即执行函数为每个 li 元素的点击事件函数创建独立作用域

    var liList = document.getElementsByTagName('li');
    for (var i = 0; i < 6; i++) {
      (function (i) {
        liList[i].onclick = function () {
          alert(i); // 0、1、2、3、4、5
        }
      })(i);
    }
    

    闭包

      闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数。闭包可以有函数名,也可以是匿名函数(没有函数名);

    闭包形式一:直接以匿名函数的形式作为外部函数的返回值(普遍用法)

    function outer() {
      var n = 1;
    
      return function() {
        n++; // 访问 outer 函数作用域中的变量 n,形成闭包
        console.log(n);
      }
    }
    
    outer()();
    

    闭包形式二:在外部函数内定义一个内部函数,并返回内部函数名

    function outer() {
      var n = 1;
    
      function inner() {
        n++; // 访问 outer 函数作用域中的变量 n,形成闭包
        console.log(n);
      }
    
      return inner;
    }
    
    outer()();
    

    闭包形式三:在外部函数内定义一个立即执行函数

    function outer() {
      var n = 1;
    
      (function() {
        n++; // 访问 outer 函数作用域中的变量 n,形成闭包
        console.log(n);
      })();
    
    }
    
    outer();
    
    闭包的作用

    作用一:可以直接访问函数作用域中的变量
    作用二:可以将函数作用域中的变量如全局变量一样始终保存在内存中 (其实能否一直保存局部变量,跟闭包的执行形式有关

    说明:作用二的解析为个人理解

    以下示例为证明观点:闭包能否一直保存局部变量,跟闭包的执行形式有关

    闭包执行方式一:在返回的闭包后面紧跟一个 () ,立即执行

    function outer() {
      var n = 1;
    
      function inner() {
        n++;
        console.log(n);
      }
    
      return inner;
    }
    
    outer()();  // 2
    outer()();  // 2  
    

    闭包执行方式二:将返回的闭包赋值给一个全局变量,全局变量运行 () 操作,执行闭包

    function outer() {
      var n = 1;
    
      function inner() {
        n++;
        console.log(n);
      }
    
      return inner;
    }
    
    var inner = outer();
    inner();    // 2
    inner();    // 3  (是在 2 的基础上加 1)
    

    以上两种执行方式单独使用,只能说明 第二种执行方式,能使闭包一直保存局部变量,但还不能证明闭包的第一种执行方式不能一直保存局部变量,因为每次执行 outer()函数时,变量 n 都被重新初始化了。

    请继续看以下代码,同时使用了闭包的两种执行方式:

    function outer() {
      var n = 1;
    
      function inner() {
        n++;
        console.log(n);
      }
    
      return inner;
    }
    
    var inner = outer();
    
    outer()();  // 2
    outer()();  // 2  (Flag1)
    
    inner();    // 2  (Flag2)
    inner();    // 3
    

    可以看到,Flag1 跟 Flag2 的值都是 2,假设 outer()() 这样的闭包执行方式会一直保存局部变量在内存的话,那 Flag2 处的值应该是 3。

    显然假设不成立。

    从而说明:闭包并非就一定可以一直保存局部变量在内存,还跟执行方式有关

    主动释放内存
    function outer() {
      var n = 1;
    
      function inner() {
        n++;
        console.log('n = ', n);
      }
    
      return inner;
    }
    
    var inner = outer();
    inner();    // 2  
    inner();    // 3
    
    inner = null;  // 解除对闭包的引用,以便释放内存
    
    闭包的应用场景
    • 保护函数局部变量的安全
    • 在内存中维持一个变量
    • 通过保护变量的安全实现JS私有属性和私有方法(不能被外部访问)

    使用闭包的注意点

      由于闭包会携带 包含它的函数 的作用域,因此会比其他函数占用跟多的内存。过度使用闭包可能会导致内存占用过多

  • 相关阅读:
    「UVA12293」 Box Game
    「CF803C」 Maximal GCD
    「CF525D」Arthur and Walls
    「CF442C」 Artem and Array
    LeetCode lcci 16.03 交点
    LeetCode 1305 两棵二叉搜索树中的所有元素
    LeetCode 1040 移动石子直到连续 II
    LeetCode 664 奇怪的打印机
    iOS UIPageViewController系统方法崩溃修复
    LeetCode 334 递增的三元子序列
  • 原文地址:https://www.cnblogs.com/uakora/p/12695429.html
Copyright © 2011-2022 走看看