JavaScript-闭包深入浅出

JavaScript中最容易的犯错的地方闭包是跑不了的,从从技术上来讲,在JavaScript中,每个function都是闭包,因为它总是能访问在它外部定义的数据。闭包(Closure)是静态语言所不具有的特性,闭包具有以下几个特点:

①闭包就是函数的局部变量集合,只是这些局部变量在函数返回后会继续存在;② 闭包就是就是函数的“堆栈”在函数返回后并不释放,我们也可以理解为这些函数堆栈并不在栈上分配而是在堆上分配③ 当在一个函数内定义另外一个函数就会产生闭包。

为了更好的理解闭包,我们可以先来简单的看一个例子:

1
2
3
4
5
6
7
8
9
10
var  scope="global";
function outerfunc() {
    var scope = "博客园-FlyElephant";
    function innerfunc() {
        console.log(scope);//博客园-FlyElephant
    }
    innerfunc();
}
outerfunc();
innerfunc();//innerfunc is not defined

在函数外部访问函数内部的嵌套函数是没法访问,函数中嵌套的函数和函数内部定义的变量处于处于同一个变量作用域中,根据作用域链的范围先从同一作用域链中寻找,如果函数内容没有定义scope变量,那么最终输出的结果应该是"global"。这只是最简单的闭包形式,如果闭包这么简单,也不会成为JavaScript中的难点。

针对上面的例子,我们稍加修改:

1
2
3
4
5
6
7
8
9
var scope = "global";
function outerfunc() {
    var scope = "博客园-FlyElephant";
    function innerfunc() {
        console.log(scope); //博客园-FlyElephant
    }
    return innerfunc;
}
outerfunc()();

结果最终输出的还是局部变量的值,这个点比较容易误解因为函数已经调用完成,局部变量应该已经不存在,输出的应该是“global”,实际上函数内部的嵌套函数对局部变量存在引用,会保持局部变量,专业一点的讲法应该是keep alive。

相信这个时候你对闭包已经稍微有点感觉,来看一下经典的例子:

1
2
3
4
5
6
7
8
9
10
11
function constfuncs() {
    var funcs = [];
    for (var i = 0; i < 10; i++) {
        funcs[i] = function() {
          console.log(i);
        };
    }
    return funcs;
}
var funcs = constfuncs();
funcs[6]();

按照我们的应该是输出的是6,但是最终输出的结果是10,通过上面的例子我们知道变量和我们定义的匿名函数都在同一个作用域,匿名函数访问的是i最终的值,i的最终值是10,因此输出的是10,单从这个例子上看可能没什么感觉,看下来的实际开发中的例子我们会理解更深刻一点:

1
2
3
4
5
6
7
8
   $(function(){
          var eles=$('.closure');
          for (var i = 0; i < eles.length; i++) {
               eles[i].onclick=function(){
                   alert(i);
               }
          }
});

毫无疑问无论点击哪个元素最终输出的结果都是元素的总数,如果我们想让上面的例子点击funcs[6]()输出6也是可以的,就是让每一个函数都拥有对应的局部作用域,看接下来的改法:

1
2
3
4
5
6
7
8
9
10
11
12
13
function constfuncs() {
    var funcs = [];
    for (var i = 0; i < 10; i++) {
        (function(i) {
            funcs[i] = function() {
                console.log(i);
            };
        })(i);
    }
    return funcs;
}
var funcs = constfuncs();
funcs[6]();

JavaScript还有个非常重要的功能是隐藏数据,这个个插件封装中用的比较多,先来看一个简单的计数器:

1
2
3
4
5
6
7
8
9
var counter = (function() {
    var count = 0;
    return function() {
        console.log(count);
        return count++;
    };
}());
counter();
counter();

这个输出的是0,1,而不是0,0,至于原因文中末尾会给出解释,看一下数据封装的升级版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var db = (function() {
// 创建一个隐藏的object, 这个object持有一些数据
// 从外部是不能访问这个object的
var data = {};
// 创建一个函数, 这个函数提供一些访问data的数据的方法
return function(key, val) {
    if (val === undefined) { return data[key] } // get
    else { return data[key] = val } // set
    }
// 我们可以调用这个匿名方法
// 返回这个内部函数,它是一个闭包
})();
 
db('x'); // 返回 undefined
db('x', 1); // 设置data['x']为1
db('x'); // 返回 1
// 我们不可能访问data这个object本身
// 但是我们可以设置它的成员

这里面用到一个小技巧就是让函数成为一个立即调用执行的表达式,然后通过内部函数保持外部变量,如果不是立即调用执行的状态,我们会发现每次都是一个新的函数,无法保持数据状态:

1
2
3
4
5
6
7
8
9
var counter = function() {
    var count = 0;
    return function() {
        console.log(count);
        return count++;
    };
};
counter()();
counter()();

关于立即调用的函数表达式也有些人直接称之为自执行的匿名函数(self-executing anonymous functions),其实关于立即调用的函数表达式有十几种书写的方式,我们常见的就是左括号和右括号两种:

1
2
(function(){console.log('1');})()
(function(){console.log('2');}())

其他的书写方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Either of the following two patterns can be used to immediately invoke
  // a function expression, utilizing the function's execution context to
  // create "privacy."
 
  (function(){ /* code */ }()); // Crockford recommends this one
  (function(){ /* code */ })(); // But this one works just as well
 
  // Because the point of the parens or coercing operators is to disambiguate
  // between function expressions and function declarations, they can be
  // omitted when the parser already expects an expression (but please see the
  // "important note" below).
 
  var i = function(){ return 10; }();
  true && function(){ /* code */ }();
  0, function(){ /* code */ }();
 
  // If you don't care about the return value, or the possibility of making
  // your code slightly harder to read, you can save a byte by just prefixing
  // the function with a unary operator.
 
  !function(){ /* code */ }();
  ~function(){ /* code */ }();
  -function(){ /* code */ }();
  +function(){ /* code */ }();
 
  // Here's another variation, from @kuvos - I'm not sure of the performance
  // implications, if any, of using the `new` keyword, but it works.
  // http://twitter.com/kuvos/status/18209252090847232
 
  new function(){ /* code */ }
  new function(){ /* code */ }() // Only need parens if passing arguments

参考链接:http://benalman.com/news/2010/11/immediately-invoked-function-expression/#iife

posted @   Fly_Elephant  阅读(870)  评论(2编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· .NET周刊【3月第1期 2025-03-02】
· [AI/GPT/综述] AI Agent的设计模式综述
点击右上角即可分享
微信分享提示