• JS进阶理解闭包


    定义

    闭包是指函数声明时所处作用域外被调用的函数。所以闭包也是函数,只不过要满足3个条件才叫闭包:

    1. 访问函数所处作用域。
    2. 函数嵌套。因为只有函数嵌套才能创建不同的作用域。
    3. 函数所处作用域外被调用。
    function foo() {
      var a = 1;
      function foo2() {
        console.log(a); // 1
      }
      return foo2;
    }
    foo()();
    
    // 等价于
    function foo() {
    var a = 1;
      return function foo2() {
       console.log(a); // 1
      }
    }
    foo()();
    

    示例中在全局作用域中被调用的foo2函数就是一个闭包。foo2声明时所处的作用域就是foo函数作用域,它在foo函数作用域外的全局作用域中被调用,并且foo2中的变量a访问了foo2所处的作用域,满足闭包的三个条件。

    IIFE立即执行函数

    函数定义后立即被调用的函数就是立即执行函数,全称是立即调用的函数表达式IIFE(Imdiately Invoked Function Expression)

    严格来说IIFE不是闭包,因为不满足闭包的三个条件。但是如果在IIFE中嵌套了函数,那就另当别论了。

    IIFE的常见形式

    // 常用
    (function(){ /* code */ }()); 
    (function(){ /* code */ })();
    
    // 其他
    var i = function(){ /* code */ }();
    true && function(){ /* code */ }();
    0, function(){ /* code */ }();
    
    !function(){ /* code */ }();
    ~function(){ /* code */ }();
    -function(){ /* code */ }();
    +function(){ /* code */ }();
    
    new function(){ /* code */ };
    new function(){ /* code */ }();  
    

    注意: JS引擎规定,如果function关键字出现在首行,一律解释成函数声明。所以function关键字前面添加!、~、-、+符号,是为了让引擎把它解释成一个表达式。

    // 函数声明必须要定义函数名称,所以报错
    function(){ /* code */ }(); // Uncaught SyntaxError: Unexpected token (
    
    // 这个并不是函数调用,而是函数声明和分组操作符的组合。由于分组操作符不能为空,所以报错。
    function foo(){ /* code */ }(); // Uncaught SyntaxError: Unexpected token )
    
    // 这样虽然不报错,但是没什么意义。
    function foo(){ /* code */ }(1); 
    
    // 函数表达式
    !function(){ /* code */ }();
    

    注意: 函数表达式和IIFE使用时,表达式后面的分号必须要有。

    // 示例1
    var a = function(){
       return 1;
    }
    
    (function(){
        console.log(1);// 报错
    })();
    
    // 示例2
    var a = function(){
       return 1;
    };
    
    (function(){
        console.log(1);// 正常
    })();
    

    闭包的常见形式

    返回值

    var foo = function(){
      var a = 1;
      var foo2 = function() {
        return a;
      }
      return foo2;
    }
    var fn = foo();
    console.log(fn()); // 1
    

    函数赋值

    把内部函数赋值给外部变量

    var fn;
    var foo = function() {
      var a = 1;
      var foo2 = function() {
        return a;
      }
      fn = foo2;
    }
    foo();
    console.log(fn()); // 1
    

    函数参数

    通过把内部函数作为参数传递给外部函数的形式实现闭包。

    var fn = function(f) {
      console.log(f()); // 1
    };
    
    var foo = function() {
      var a = 1;
      var foo2 = function() {
       return a;
      }
      fn(foo2);
    };
    foo();
    

    上面的3种闭包形式,foo函数声明后是立即被调用的,所以上面的示例可以用IIFE表示。

    var fn = (function(){
      var a = 1;
      var foo2 = function() {
       return a;
      }
      return foo2;
    })();
    
    console.log(fn()); // 1
    
    var fn;
    var foo = (function() {
      var a = 1;
      var foo2 = function() {
        return a;
      }
      fn = foo2;
    })();
    
    console.log(fn()); // 1
    
    var fn = function(f) {
      console.log(f()); // 1
    };
    
    var foo = (function() {
      var a = 1;
      var foo2 = function() {
        return a;
      }
      fn(foo2);
    })();
    

    闭包的常见用途

    循环赋值

    function foo() {
      var arr = [];
      for(var i = 0; i < 3; i++) {
       arr[i] = function() {
         return i;
       }
      }
      return arr;
    }
    
    var ret = foo();
    console.log(ret[0]()); // 3
    

    上面的循环,没有出现预想的结果0,原因是在调用匿名函数时,通过作用域找到的不是循环时候的瞬间值,而是循环结束后的索引值。

    正确写法:

    function foo() {
      var arr = [];
      for(var i = 0; i < 3; i++) {
       arr[i] = (function(j) {
         return function() {
           return j;
         };
       })(i)
      }
      return arr;
    }
    
    var ret = foo();
    console.log(ret[0]()); // 0
    

    变量私有化

    通过闭包把变量私有化,避免污染全局作用局。

    var getVal, setVal;
    
    (function(){
      var secret = 0;
      getVal = function() {
       return secret;
      }
      setVal = function(v) {
       secret = v;
      }
    })()
    
    setVal(3);
    getVal(); // 3
    

    迭代器

    迭代器

    function setup(arr) {
      var i = 0;
      return function() {
       return arr[i++];
      }
    }
    var next = setup(['a','b','c']);
    next(); // 'a'
    next(); // 'b'
    next(); // 'c'
    

    累加器

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

    闭包的缺点

    使用闭包是有一定代价的,闭包函数所处作用域中的变量会一直存在于内存中,这是为了保证闭包在调用的时候,能够通过作用域链找到这个变量,直到页面关闭内存才会被释放。

    由于闭包占用内存空间,所以使用闭包要尽量少用。并且使用结束后通过把值赋值为null解除引用,以便尽早释放内存。

    var foo = function(){
      var a = 1;
      var foo2 = function() {
       return a;
      }
      return foo2;
    }
    var fn = foo();
    fn();
    fn = null;
    优秀文章首发于聚享小站,欢迎关注!
  • 相关阅读:
    如何设计好的RESTful API 之好的RESTful API 特征
    如何设计好的RESTful API之安全性
    RESTful接口签名认证实现机制
    在eclipse中使用Lombok
    http://coolshell.cn/articles/10910.html
    http://www.cnblogs.com/hoojo/archive/2011/06/08/2075201.html
    http://jingyan.baidu.com/article/0eb457e5208cbb03f0a9054c.html
    http://blog.csdn.net/emoven/article/details/12999265
    【win8技巧】win8快速切换后台应用
    解决Linux/aix 下的websphere log4j不生效
  • 原文地址:https://www.cnblogs.com/yesyes/p/15351997.html
Copyright © 2020-2023  润新知