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

本文转自Fly_Elephant博客园博客,原文链接:http://www.cnblogs.com/xiaofeixiang/p/5035590.html,如需转载请自行联系原作者


相关文章
|
12天前
|
自然语言处理 JavaScript 前端开发
JavaScript中闭包:概念、用途与潜在问题
【4月更文挑战第22天】JavaScript中的闭包是函数及其相关词法环境的组合,允许访问外部作用域,常用于数据封装、回调函数和装饰器。然而,不恰当使用可能导致内存泄漏和性能下降。为避免问题,需及时解除引用,减少不必要的闭包,以及优化闭包使用。理解并慎用闭包是关键。
|
1月前
|
JavaScript
闭包(js的问题)
闭包(js的问题)
13 0
|
2月前
|
存储 缓存 JavaScript
|
2月前
|
自然语言处理 JavaScript 前端开发
探索JavaScript中的闭包:理解其原理与实际应用
探索JavaScript中的闭包:理解其原理与实际应用
20 0
|
2月前
|
自然语言处理 JavaScript 前端开发
深入理解JS的执行上下文、词法作用域和闭包(中)
深入理解JS的执行上下文、词法作用域和闭包(中)
|
2月前
|
存储 自然语言处理 JavaScript
深入理解JS的执行上下文、词法作用域和闭包(上)
深入理解JS的执行上下文、词法作用域和闭包(上)
|
5天前
|
自然语言处理 前端开发 JavaScript
【Web 前端】什么是JS闭包?
【4月更文挑战第22天】【Web 前端】什么是JS闭包?
|
6天前
|
自然语言处理 JavaScript 前端开发
闭包对于javascript中有什么作用
JavaScript中的闭包有多种用途,如数据封装和私有化、函数工厂及保持状态。闭包能创建私有变量和函数,防止外部访问,实现清晰的代码接口。
|
9天前
|
测试技术
js_防抖与节流(闭包的使用)
js_防抖与节流(闭包的使用)
15 0
|
12天前
|
设计模式 JavaScript 前端开发
js开发:请解释闭包(closure)是什么,以及它的用途。
【4月更文挑战第23天】闭包是JavaScript中的一个重要概念,允许函数访问并操作外部作用域的变量,常用于实现私有变量、模块化和高阶函数。私有变量示例展示了如何创建只在特定函数内可访问的计数器。模块化示例演示了如何封装变量和函数,防止全局污染。最后,高阶函数示例说明了如何使用闭包创建接受或返回函数的函数。
15 0