JavaScript 学习note (闭包 Closure)

时间:2022-06-13 22:48:01

原文地址:http://www.cnblogs.com/chiuyun/archive/2012/06/14/2476093.html 

闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了*变量的函数。这个被引用的*变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。

闭包是函数以及函数引用的变量环境(A closure (also lexical closure or function closure) is a function together with a referencing environment for the non-local variables of that function)。

通常,本地(或局部)变量只在函数执行期内存在,一旦函数执行完,本地变量就没有必要存在了,会被GC(garbage collection垃圾收集器)回收。而闭包就是要打破这种常规。

下面的代码可以显示hello world。name变量依然存在。这里myFunc就是结合了displayName函数和name变量的闭包。

function makeFunc() 
{
  var name = "Hello World";
  function displayName() 
  {
    alert(name);
  }
  return displayName;
}

var myFunc = makeFunc();
myFunc();

一.闭包的常见应用--循环绑定

想要给input的focus事件绑定显示对应的help提醒。

直观的写法,也是错误的写法,input都是绑定了最后一个help信息。

<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<p id="help">Helpful notes will appear here</p>
<p>E-mail: <input type="text" id="email" name="email"></p>
<p>Name: <input type="text" id="name" name="name"></p>
<p>Age: <input type="text" id="age" name="age"></p>
</body>
<script type="text/javascript">
function showHelp(help) 
{
  document.getElementById('help').innerHTML = help;
}

function setupHelp() 
{
  var helpText = [
      {'id': 'email', 'help': 'Your e-mail address'},
      {'id': 'name', 'help': 'Your full name'},
      {'id': 'age', 'help': 'Your age (you must be over 16)'}
    ];

  for (var i = 0; i < helpText.length; i++) 
  {
    var item = helpText[i];
    document.getElementById(item.id).onfocus = function() 
    {
      showHelp(item.help);
    }
  }
}

setupHelp();
</script>
</html>

分配给 onfocus 的回调函数是其实就是闭包,由函数的定义和从 setupHelp 函数的作用域所找到的环境变量所组成的,这三个闭包共享了同一个环境。当执行onfocus被调用的时候,循环已经执行,由三个闭包所共享的item变量指向了 helpText 列表中的最后一项。

可以实现想要功能的闭包写法,使用了更多的闭包。

<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<p id="help">Helpful notes will appear here</p>
<p>E-mail: <input type="text" id="email" name="email"></p>
<p>Name: <input type="text" id="name" name="name"></p>
<p>Age: <input type="text" id="age" name="age"></p>
</body>
<script type="text/javascript">
function showHelp(help)
{
  document.getElementById('help').innerHTML = help;
}

function makeHelpCallback(help) 
{
  return function() 
  {
    showHelp(help);
  };
}

function setupHelp() 
{
  var helpText = [
      {'id': 'email', 'help': 'Your e-mail address'},
      {'id': 'name', 'help': 'Your full name'},
      {'id': 'age', 'help': 'Your age (you must be over 16)'}
    ];

  for (var i = 0; i < helpText.length; i++) 
  {
    var item = helpText[i];
    document.getElementById(item.id).onfocus = makeHelpCallback(item.help);
  }
}
setupHelp();
</script>
</html>

makeHelpCallback 函数给每一个focus的回调函数建立了新的环境, help 变量就指向了相对应的 helpText 数组中的字符串。这里用到了工厂方法( factory function )。

二.闭包--工厂方法

工厂方法设计模式(factory method pattern)是在OO(面向对象 object-oriented)程序设计中,工厂类根据传入的参数,动态决定应该创建哪一个产品类的实例。

下面的闭包例子,就本质而言,makeAdder是一个工厂函数, 它可以生成一个可以把指定的值和自己的参数相加并返回的函数。使用了工厂函数制来建立两个新的函数 : 一个给它自己的参数加上 5,另一个则加上 10。

add5 和 add10 两个都是闭包。他们共享相同的函数体的定义,但保存了不同的环境。在 add5 的环境中,x 是 5;add10的环境中,x 是 10。

function makeAdder(x) 
{
  return function(y) 
  {
    return x + y;
  };
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

三.闭包--模拟私有方法

像 c++, Java, c# 这类语言可以把方法声明为私有,限制这些方法只能被同一类别的其他方法所调用。

JavaScript 没有提供做这些事的原生方式,但可以使用闭包来模拟私有方法。私有方法可以限制代码的调用,也可以管理命名空间,把非必要的方法与公开的接口隔离。

下面是使用闭包定义可以调用私有方法和变量的公开方法。

var makeCounter = function() 
{
  var privateCounter = 0;
  function changeBy(val) 
  {
    privateCounter += val;
  }
  return {
    increment: function() 
    {
      changeBy(1);
    },
    decrement: function() 
    {
      changeBy(-1);
    },
    value: function() 
    {
      return privateCounter;
    }
  }  
};

var Counter1 = makeCounter();
var Counter2 = makeCounter();
alert(Counter1.value()); /* Alerts 0 */
Counter1.increment();
Counter1.increment();
alert(Counter1.value()); /* Alerts 2 */
Counter1.decrement();
alert(Counter1.value()); /* Alerts 1 */
alert(Counter2.value()); /* Alerts 0 */

alert(typeof(privateCounter)); /* undefined */
alert(typeof(Counter1.privateCounter)); /* undefined */

除非必要,不要过多的使用闭包实现功能,闭包在执行效率和内存资源占用有负面影响。

 

 

学习资料:https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Working_with_Closures