1

闭包定义

对闭包的具体定义有很多种说法,这些说法大体可以分为两类:

  1. 闭包是其词法上下文中引用了自由变量的函数.

  2. 闭包是由函数和其相关的引用环境组合而成的实体.

词法:变量的作用域是由它在源码中所处位置决定的.

很多人都觉得闭包是一个很难理解的知识点,其实不然,不管它的定义有多难理解,我们只需自己对它形成一种自己可以理解的定义就可以了,并保证这种自我理解定义的正确性和可行性.

在这里来看,闭包不管他是一个函数还是一个实体,它给我的理解就是一个函数可以访问当前上下文的环境变量.在这样看来,不管是函数内部的函数,还是单个定义的函数,有函数的地方就存在闭包.

闭包的特性和好处

闭包的特性:

  • 函数内部可以引用外部的参数和变量.

  • 参数和变量不会被垃圾回收机制回收.

使用闭包的好处

  • 希望一个变量长期保存在内存中

  • 可以拥有私有成员

  • 在函数内部存在函数形式的闭包中,可以避免全局变量的污染

举例

函数内部函数

function init() {
  var name = "closure";
  function displayName() {
    alert(name);
  }
  displayName();
}
init();

函数 init() 创建了一个局部变量 name,然后定义了名为 displayName() 的函数。 displayName() 是一个内部函数——定义于 init() 之内且仅在该函数体内可用。displayName() 没有任何自己的局部变量,然而它可以访问到外部函数的变量,即可以使用父函数中声明的 name 变量。

用闭包模拟私有方法

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()); /* 提示 0 */
Counter1.increment();
Counter1.increment();
alert(Counter1.value()); /* 提示 2 */
Counter1.decrement();
alert(Counter1.value()); /* 提示 1 */
alert(Counter2.value()); /* 提示 0 */

请注意两个计数器是如何维护它们各自的独立性的。每次调用 makeCounter() 函数期间,其环境是不同的。每次调用中, privateCounter 中含有不同的实例。
这种形式的闭包提供了许多通常由面向对象编U所享有的益处,尤其是数据隐藏和封装。

在循环中创建闭包:一个常见错误

<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>
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();

运行这段代码后,您会发现它没有达到想要的效果。无论焦点在哪个输入域上,显示的都是关于年龄的消息,
该问题的原因在于赋给 onfocus 是闭包(setupHelp)中的匿名函数而不是闭包对象;

解决这个问题的一种方案是使onfocus指向一个新的闭包对象。

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();

性能考量

如果不是因为某些特殊任务而需要闭包,在没有必要的情况下,在其它函数中创建函数是不明智的,因为闭包对脚本性能具有负面影响,包括处理速度和内存消耗。

例如,在创建新的对象或者类时,方法通常应该关联于对象的原型,而不是定义到对象的构造器中。原因是这将导致每次构造器被调用,方法都会被重新赋值一次(也就是说,为每一个对象的创建)。

考虑以下虽然不切实际但却说明问题的示例:

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
  this.getName = function() {
    return this.name;
  };

  this.getMessage = function() {
    return this.message;
  };
}

上面的代码并未利用到闭包的益处,因此,应该修改为如下常规形式:

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype = {
  getName: function() {
    return this.name;
  },
  getMessage: function() {
    return this.message;
  }
};

yuhualingfeng
1.7k 声望49 粉丝

前端极致追求者


下一篇 »
Javascript简介