Javascript闭包的一些研究

简介: 原文:Javascript闭包的一些研究    本文不谈闭包的概念,因为概念容易把人搞晕,本文希望通过几个鲜活的例子来探究闭包的性质,相信对理解闭包会有所帮助。   程序1 var f = (function() { var n = 10; return function() { ++n; console.log(n); } })(); f(); 输出: 11 结论: 闭包函数可以访问外层函数中的变量。
原文: Javascript闭包的一些研究

 

 

本文不谈闭包的概念,因为概念容易把人搞晕,本文希望通过几个鲜活的例子来探究闭包的性质,相信对理解闭包会有所帮助。

 

程序1

var f = (function() {
     var n = 10;
     return function() {
          ++n;
          console.log(n);
     }
})();

f();

输出:

11

结论:

闭包函数可以访问外层函数中的变量。

 

程序2

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

for (var i=0; i<3; ++i) {
     var f = arr[i];
     f();
}

 

输出:

1
2
3

 

结论:

一个函数内有多个闭包函数,那么这些闭包函数共享外层函数中的变量。可以看出,例子中3个闭包函数中的n是同一个变量,而不是该变量的3个副本。 

 

程序3

var f0 = function() {
  ++n;
  console.log(n);
}

var f = (function() {
     var n = 10;
     return f0;
})();

f();

 

输出:

错误指向“++n”这一行。

 

结论:

闭包函数的作用域不是在引用或运行它的时候确定的,看起来好像是在定义它的时候确定的。

 

说明:
虽然该程序与“程序1”看起来一样,但由于函数f0一个在内部定义一个在外部定义,尽管它们都是从函数内部返回,但在这个例子中f0却无法访问变量n。

 

程序4

var f = (function() {
     var n = 10;
     return new Function('++n;console.log(n);');
})();

f();

 

输出同“程序3”:

 

结论:

该程序是对“程序3”的进一步印证和补充。由该程序可以得出的结论是:闭包函数的作用域是在编译它的时候确定的。

 

说明:
使用Function构造器可以创建一个function对象,函数体使用一个字符串指定,它可以像普通函数一样执行,并在首次执行时编译。因此,虽然在匿名函数内部定义了此function对象,但一直要到调用它的时候才会编译,即执行到“f()”时,而此时原函数的作用域已经不存在了。

 

再看一个例子:

程序5

var f = (function() {
     var n = 10;
     return eval('(function(){++n;console.log(n);})');
})();

f();

 

输出:

11

 

结论:无

 

说明:
这个例子是对上面两个程序的补充。这个例子之所以能够和“程序1”一样打印出11,是因为eval( )函数会立即对传递给它字符串进行解析(编译、执行),因此使用eval定义的函数和直接定义的效果是等价的。

(注意:eval( )中的“function(){...}”必须用括号扩起来,否则会报错)

 

程序6

var f = (function() {
     var n = 10;
     var s = 'hello';
     return function() {
          ++n;
          console.log(n);
     }
})();

f();

 

运行时在“console.log(n);”这一行加个断点,查看作用域中的值,其中只有n没有s:


结论:
外层函数中那些未在闭包函数中使用的变量,对闭包函数是不可见的。在这个例子中,闭包函数没有引用过变量s,因此其作用域中只有n。也就是说,对闭包函数来说,其可以访问的外层函数的变量实际上只是真正的外层函数变量的一个子集。

 

程序7

这个程序用来通过数据证明“程序6”的结论。程序稍微有点复杂,后面会先对其做简单说明。

var funArr = [];
var LENGTH = 500;
var ALPHABET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';

function getStr( ) {
     var s = '';
     for (var i=0; i<LENGTH; ++i) {
          s += ALPHABET[Math.floor(Math.random( ) * 62)];
     }
     return s;
}

function getArr( ) {
     var a = new Array(LENGTH);
     for (var i=0; i<LENGTH; ++i) {
          a[i] = Math.random( );
     }
}

var f = function( ) {
     var n = 10;
     var s = getStr( );
     var a = getArr( );

     funArr.push(function( ) {
          console.log(n, s, a);  // --- 1
          console.log(n);        //  --- 2
     })
}

for (var i=0; i<2000; ++i) {
     f( );
}

 

程序分析:

本程序的重点是for循环和函数f。for循环中调用了函数f 2000次,每次调用都会创建一个数字和两个长度为500的字符串和数组,所以2000次函数调用所创建的局部变量的规模还是比较可观的,程序用这种方法以便于后面做分析时对结果进行比较。

f中的局部变量会被一个闭包函数所引用,以此观察未被引用的局部变量是否会被回收。

分别运行该程序两次,第一次使用语句1(引用了f中的所有局部变量),第二次使用语句2(只引用了数字变量n)。对运行所得到的结果1和结果2分别采集堆快照(Heap Snapshot):

 

可以看到所占内存差别巨大,从这里就可以初步得出“未被闭包函数引用的局部变量会被回收”的结论。

不过为了严谨性,需要做更细致地分析。首先是结果1和结果2的统计图:

 

 

 

可以看到,第二次运行后内存中的对象数量明显要比第一次的少很多(二者产生的中间对象数量是相同的),这进一步说明了第二次运行后大部分对象都被回收了。

最后我们将结果2与结果1进行细致的比较,结果如下:


结论:
函数中的局部变量如果没有被任何闭包函数所引用(这里不考虑被全局变量所引用的情况),则这些局部变量在函数运行完成后就可以被回收,否则,这些变量会作为其闭包函数的作用域的一部分被保留,直到所有闭包函数也执行完毕为止。

该结论同时也印证了“程序6”的结论:闭包函数对外层函数作用域的引用是外层函数真实作用域的一个子集。

另外从这个实验还能推测出一点,即外层函数执行结束后是会被回收掉的。因为既然函数内部变量已被回收,那函数本身也没有存在的意义了。

 

目录
相关文章
|
22天前
|
JavaScript
闭包(js的问题)
闭包(js的问题)
12 0
|
1月前
|
设计模式 JavaScript 前端开发
js开发:请解释闭包(closure)是什么,以及它的用途。
闭包是JavaScript中的关键特性,允许函数访问并操作外部作用域的变量,常用于实现私有变量、模块化和高阶函数。私有变量示例展示了如何创建无法外部访问的计数器;模块化示例演示了封装私有变量和函数,防止全局污染;高阶函数示例则说明了如何使用闭包创建能接收或返回函数的函数。
15 2
|
1月前
|
存储 缓存 JavaScript
|
1月前
|
自然语言处理 JavaScript 前端开发
探索JavaScript中的闭包:理解其原理与实际应用
探索JavaScript中的闭包:理解其原理与实际应用
19 0
|
1月前
|
自然语言处理 JavaScript 前端开发
深入理解JS的执行上下文、词法作用域和闭包(中)
深入理解JS的执行上下文、词法作用域和闭包(中)
|
1月前
|
存储 自然语言处理 JavaScript
深入理解JS的执行上下文、词法作用域和闭包(上)
深入理解JS的执行上下文、词法作用域和闭包(上)
|
1天前
|
测试技术
js_防抖与节流(闭包的使用)
js_防抖与节流(闭包的使用)
7 0
|
29天前
|
JavaScript 前端开发 Java
深入剖析 JavaScript 闭包
深入探讨JavaScript闭包,了解其定义、特性、优缺点及作用。闭包是函数与其引用环境的组合,允许内层函数访问外层作用域,常驻内存可能导致内存泄露。优点包括创建私有变量,缺点则涉及内存使用。闭包在变量搜索中遵循从内到外的规则,并影响变量的作用域和生存周期。理解闭包有助于优化代码并避免性能问题。
21 1
|
1月前
|
自然语言处理 前端开发 JavaScript
深入理解JavaScript中的闭包与作用域链
在JavaScript编程中,闭包和作用域链是两个非常重要的概念,它们对于理解代码的执行过程和解决一些特定问题至关重要。本文将深入探讨JavaScript中闭包和作用域链的原理和应用,帮助读者更好地理解这些概念并能够在实际项目中灵活运用。
|
1月前
|
JavaScript 前端开发
javascript闭包的理解(菜菜必看系列!!!)
javascript闭包的理解(菜菜必看系列!!!)