闭包

闭包是指独立的(免费的)变量的函数。

换句话说,该函数闭包中定义的“回忆”的环境创建它。

考虑以下:

函数 初始化() {
    var name = “火狐”;/ /名字是一个局部变量由init    函数 displayName() {/ / displayName内部函数(),一个闭包        alert (name);/ / displayName()使用变量声明在父函数    }
    displayName();    
}
初始化();
 

 

init()创建一个局部变量name然后一个函数调用displayName()displayName()是一个内部函数在定义init()的身体内,只有该函数。displayName()没有自己的本地变量,然而它访问外部函数的变量,所以可以使用变量name在父函数声明。

运行的代码,看看这工作。 这是一个例子词汇 范围:在JavaScript中,一个变量的范围被定义为它的位置在源代码(这是明显的词法)和嵌套函数可以访问变量声明外的范围。

现在考虑一下下面的例子:

函数 makeFunc() {
  var name = “火狐”;
  函数 displayName() {
    警报(name);
  }
  返回 displayName;
}

var myFunc = makeFunc();
myFunc();
 

如果运行这段代码将会与前面相同的效果init()例如:字符串“Mozilla”将会显示在一个JavaScript警告框。 有什么不同和有趣的是displayName()内部函数从外部函数返回之前被执行。

仍能工作的代码看起来不直观。 通常,函数内的局部变量只存在期间,函数的执行。 一次makeFunc()执行完成,这是合理的期望,变量名称将不再访问。 由于代码仍然是预期,这显然不是如此。

这个难题的解决之道myFunc已成为一个关闭。 闭包是一种特殊的对象,结合两件事:一个函数,这个函数创建的环境。 环境包括所有局部变量作用范围内的时候,关闭了。 在这种情况下,myFunc是一个闭包,包含了吗displayName功能和“Mozilla”存在关闭时创建的字符串。

这是一个稍微有趣的例子——一个makeAdder功能:

函数 makeAdder(x) {
  返回 函数(y) {
    返回 x + y;
  };
}

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

console日志(的折扣(2)); / / 7console日志(add10(2));/ / 12
 

在这个例子中,我们定义了一个函数makeAdder(x)它接受一个参数吗x并返回一个新的函数。 该函数返回一个参数y,并返回的总和xy

从本质上讲,makeAdder工厂是一个函数,它创建功能,可以添加一个特定值的参数。 在上面的示例中,我们使用我们的函数工厂创建两个新的功能,增加5到它的参数,并增加了10。

add5add10都是闭包。 他们共享相同的函数体定义,但存储不同的环境。 在add5的环境中,x是5。 至于add10而言,x是10。

实际的闭包

这是理论的,但实际上是闭包有用吗? 让我们考虑他们的实际意义。 闭包可以将一些数据(环境)与一个数据的函数来操作。 这有明显的相似之处面向对象编程,对象允许我们将一些数据(对象的属性)与一个或多个方法。

因此,你可以在任何地方使用闭包,您通常会使用一个对象只有一个方法。

情况下,您可能想要在网络上这样做尤其常见。 我们写的代码在web JavaScript是基于事件——我们定义一些行为,然后将它附加到一个事件触发的用户(如单击或键盘按键)。 我们的代码通常是附加一个回调:一个函数执行在响应事件。

这里有一个实际的例子:假设我们希望添加一些按钮的页面调整文本大小。 这样做的方法之一是body元素的指定字体大小的像素,然后设置页面上的其他元素的大小(如标题)使用相对em单位:

身体{
  字体类型: Helvetica, Arial, sans-serif;
  字体大小: 12px;
}

h1 {
  字体大小: 1.5em;
}

h2 {
  字体大小: 1.2em;
}
 

我们的互动文本大小按钮可以改变身体的字体大小属性元素,和调整将被其他页面元素由于相对单位。

JavaScript:

函数 makeSizer(size) {
  返回 函数() {
    document。body。style。fontSize = size + “像素”;
  };
}

var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);
 

size12, size14,size16正文现在功能将调整至12日14日分别和16个像素。 我们可以将它们附加到按钮(在这种情况下链接)如下:

documentgetElementById(12码的)。onclick = size12;
documentgetElementById(14码的)。onclick = size14;
documentgetElementById(16号的)。onclick = size16;
 
<一个 href=”#id=”12码>12< /一个>
<一个 href=”#id=”14码>14< /一个>
<一个 href=”#id=”16号>16< /一个>
 

JSFIDDLE观

 

模拟私有方法闭包

语言比如Java提供私人声明方法的能力,这意味着他们只能被其他方法在同一个班。

JavaScript不提供一个本地这样做的方法,但是可以效仿私有方法使用闭包。 私有方法不仅仅适用于限制访问代码:他们还提供强大的管理你的全局名称空间的方式,防止不必要的方法弄乱公共接口的代码。

下面是如何定义一些公共的函数可以访问私有函数和变量,这也被称为使用闭包模块的模式:

var counter = (函数() {
  var privateCounter = 0;
  函数 changeBy(val) {
    privateCounter += val;
  }
  返回 {
    increment: 函数() {
      changeBy(1);
    },
    decrement: 函数() {
      changeBy(- - - - - -1);
    },
    value: 函数() {
      返回 privateCounter;
    }
  };   
})();

警报(counter价值()); 0 * / / *警报
counter增量();
counter增量();
警报(counter价值()); 2 * / / *警报
counter减量();
警报(counter价值()); 1 * / / *警报
 

这里发生了很多事情。 在之前的例子每个关闭了自己的环境,在这里,我们创建一个单独的环境共享的三个功能:counter.increment,counter.decrement,counter.value

共享环境中创建一个匿名函数的函数体,这是执行就被定义。 环境包含两个私人物品:一个变量privateCounter调用和函数changeBy。 这些私人物品可以直接从外部访问的匿名函数。 相反,他们必须通过三个公共访问的函数返回的匿名的包装器。

这三个公共函数闭包,共享相同的环境。 由于JavaScript的词法作用域,他们每一个访问privateCounter变量和changeBy功能。

你会注意到我们定义一个匿名函数,创建了一个计数器,然后我们称之为立即和分配结果makeCounter变量。 我们可以将这个函数存储在一个单独的变量,并使用它来创建几个柜台。

var makeCounter = 函数() {
  var privateCounter = 0;
  函数 changeBy(val) {
    privateCounter += val;
  }
  返回 {
    increment: 函数() {
      changeBy(1);
    },
    decrement: 函数() {
      changeBy(- - - - - -1);
    },
    value: 函数() {
      返回 privateCounter;
    }
  }  
};

var counter1 = makeCounter();
var counter2 = makeCounter();
警报(counter1价值()); 0 * / / *警报
counter1增量();
counter1增量();
警报(counter1价值()); 2 * / / *警报
counter1减量();
警报(counter1价值()); 1 * / / *警报
警报(counter2价值()); 0 * / / *警报
 

注意两个计数器维护其独立性。 其环境的呼唤makeCounter()功能是不同的。 闭包变量privateCounter 每次都包含一个不同的实例。

以这种方式使用闭包提供了许多好处,通常与面向对象编程有关,特别是数据隐藏和封装。

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

之前的介绍let关键字在JavaScript 1.7中,一个常见的问题与关闭时发生创造了在一个循环。 考虑下面的例子:

<p id=”帮助>Helpful notes will appear here< /p>
<p>E-mail: <输入 类型=”文本id=”电子邮件的名字=”电子邮件>< /p>
<p>Name: <输入 类型=”文本id=”的名字的名字=”的名字>< /p>
<p>Age: <输入 类型=”文本id=”年龄的名字=”年龄>< /p>
 
函数 showHelp(help) {
  documentgetElementById(“帮助”)。innerHTML = help;
}

函数 setupHelp() {
  var helpText = (
      {“id”: “电子邮件”, “帮助”: 您的电子邮件地址的},
      {“id”: “名字”, “帮助”: “你的姓名”},
      {“id”: “年龄”, “帮助”: “你的年龄(您必须在16)”}
    ];

  (var i = 0; i < helpText。length; i+ +) {
    var item = helpText(i];
    documentgetElementById(item。id)。onfocus = 函数() {
      showHelp(item。help);
    }
  }
}

setupHelp();
 

JSFIDDLE观

helpText数组定义了三个有用的提示,每个相关ID输入字段的文档。 通过这些定义循环周期,鬼混onfocus事件每一个显示相关帮助的方法。

如果你尝试这段代码,你会发现它不正常工作。 不管你关注的领域,信息将显示你的年龄。

原因是分配给得到焦点的功能关闭,它们包括函数的定义和捕获的环境setupHelp函数的范围。 创建了三个闭包,但每一个共享相同的单一环境。 这些回调函数执行时,循环结束,项目变量(共享的所有三个闭包)指向最后一个条目helpText列表。

在这种情况下一个解决方案是使用更多的闭包:特别是,使用工厂函数如前所述:

函数 showHelp(help) {
  documentgetElementById(“帮助”)。innerHTML = help;
}

函数 makeHelpCallback(help) {
  返回 函数() {
    showHelp(help);
  };
}

函数 setupHelp() {
  var helpText = (
      {“id”: “电子邮件”, “帮助”: 您的电子邮件地址的},
      {“id”: “名字”, “帮助”: “你的姓名”},
      {“id”: “年龄”, “帮助”: “你的年龄(您必须在16)”}
    ];

  (var i = 0; i < helpText。length; i+ +) {
    var item = helpText(i];
    documentgetElementById(item。id)。onfocus = makeHelpCallback(item。help);
  }
}

setupHelp();
 

JSFIDDLE观

这是预期。 而不是回调都共享一个单一的环境,makeHelpCallback为每一个函数创建一个新的环境中help指从相应的字符串helpText数组中。

性能注意事项

是不明智的不必要的创建函数在其他函数如果闭包不需要一个特定的任务,因为它会影响脚本性能的处理速度和内存消耗。

例如,当创建一个新对象/类,方法通常应该关联对象的原型,而不是定义为对象的构造函数。 原因是每当构造函数被调用时,会重新分配的方法(即每一个对象创建)。

考虑以下不切实际但示范案例:

函数 MyObject(name, message) {
  。name = nametoString();
  。message = messagetoString();
  。getName = 函数() {
    返回 。name;
  };

  。getMessage = 函数() {
    返回 。message;
  };
}
 

前面的代码不利用闭包的好处,从而可以制定:

函数 MyObject(name, message) {
  。name = nametoString();
  。message = messagetoString();
}
MyObject。prototype = {
  getName: 函数() {
    返回 。name;
  },
  getMessage: 函数() {
    返回 。message;
  }
};
 

然而,重新定义原型不推荐,所以下面的例子就更好了,因为它附加到现有的原型:

函数 MyObject(name, message) {
  。name = nametoString();
  。message = messagetoString();
}
MyObject。prototype。getName = 函数() {
  返回 。name;
};
MyObject。prototype。getMessage = 函数() {
  返回 。message;
};
 

在前面的两个例子中,继承的原型可以共享所有对象和方法定义不需要出现在每一个对象创建。 看到对象模型的细节为更多的细节。

表达式闭包

这只不过是一个速记写简单的功能,给类似于一个典型的语言Lambda的符号

JavaScript 1.7及以上:

函数(x) { 返回 x * x; }
 

JavaScript 1.8:

函数(x) x * x
 

这个语法允许你离开了括号,“返回”声明——隐式。 没有额外的好处以这种方式编写代码,除了有语法更短。

例子:

简称绑定事件监听器:

documentaddEventListener(“点击”, 函数() , 真正的);
 

使用这个符号的数组函数从JavaScript 1.6:

elems一些(函数(elem) elem。type = = “文本”);


原文地址:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures

posted @ 2014-10-07 13:45  Matedo  阅读(138)  评论(0编辑  收藏  举报