博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
(JS基础)闭包
阅读量:5946 次
发布时间:2019-06-19

本文共 2960 字,大约阅读时间需要 9 分钟。

词法作用域:

首先我们看一个最简单的例子:

var x = 100;function fn(){  console.log(x);}fn();    // 100复制代码

毫无疑问,"fn()" 函数是可以访问到外部定义的变量 "x"。函数被创建时,都会创建其"作用域"。"fn" 函数被创建,其作用域内未声明变量 "x" ,只能到上一级的作用域(这里是全局作用域)找,这里的 "x" 称之为 "自由变量"

简单的闭包:

function fn() {  var x = 10;  return function (n) {    return x > n ? x : n  }}let moreThanTen = fn()console.log( moreThanTen(9) )console.log( moreThanTen(11) )复制代码

上述例子中,"fn" 内部创建了变量 "x" 和返回一个匿名函数,匿名函数使用到父级的自由变量 "x" 。

正常来说,第七行执行 "fn" 函数之后,其函数作用域应该销毁,但匿名函数需要用到父级变量,因而产生了闭包。

闭包是由函数以及创建该函数的词法环境组合而成。这个环境包含了这个闭包创建时所能访问的所有局部变量

用闭包模拟私有方法:

var Counter = (function() {  // 私有变量  var privateCounter = 0;  // 私有方法,用于修改私有变量  function changeBy(val) {    privateCounter += val;  }  return {    increment: function(n) {      changeBy(n);    },    decrement: function(n) {      changeBy(-n);    },    value: function() {      return privateCounter;    }  }   })();console.log(Counter.value());    // 0Counter.increment(3);console.log(Counter.value());    // 3Counter.decrement(1);console.log(Counter.value());    // 2复制代码

这个环境中包含两个私有项:名为 privateCounter 的变量和名为 changeBy 的函数。这两项都无法在这个匿名函数外部直接访问。必须通过匿名函数返回的三个公共函数访问。这三个公共函数是共享同一个环境的闭包。

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

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

首先看一个官方错误:

Helpful notes will appear here

E-mail:

Name:

Age:

复制代码
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();复制代码

运行这段代码后,您会发现它没有达到想要的效果。无论焦点在哪个input上,显示的都是关于年龄的信息。

原因是赋值给 onfocus 的是闭包。这些闭包是由他们的函数定义和在 setupHelp 作用域中捕获的环境所组成的。这三个闭包在循环中被创建,但他们共享了同一个词法作用域,在这个作用域中存在一个变量item。当onfocus的回调执行时,item.help的值被决定。由于循环在事件触发之前早已执行完毕,变量对象item(被三个闭包所共享)已经指向了helpText的最后一项。可以理解为,三个 onfocus 函数共享一个闭包环境

最为简单的解决办法是把循环中的 "var" 关键字改为 "let"。如下:

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

或者使用匿名闭包:

for (var i = 0; i < helpText.length; i++) {  (function() {    var item = helpText[i];    document.getElementById(item.id).onfocus = function() {      showHelp(item.help);    }  })(); // 马上把当前循环项的item与事件回调相关联起来}复制代码

性能考量:

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

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

关于循环体内的 "var" 与 "let" :

function fnVar() {  for (var i = 0; i < 5; i++) { }  console.log(i)}function fnLet() {  for (let i = 0; i < 5; i++) { }  console.log(i)}fnVar()       // 5// fnLet()    // 会报错 复制代码

在ES6 之前,JavaScript 是没有块级作用域,上述例子证明了 "var" 声明的变量在块作用域仍然存在。

ES6 中新增的 "let" 关键字提供了块级作用域,其声明的变量在块外不能访问。

附:。

转载于:https://juejin.im/post/5c97287b6fb9a070c40ad278

你可能感兴趣的文章
UVA 122 Trees on the level 二叉树 广搜
查看>>
POJ-2251 Dungeon Master
查看>>
tortoisesvn的安装
查看>>
我是怎么使用最短路径算法解决动态联动问题的
查看>>
URAL 1353 Milliard Vasya's Function DP
查看>>
速读《构建之法:现代软件工程》提问
查看>>
Android onclicklistener中使用外部类变量时为什么需要final修饰【转】
查看>>
django中聚合aggregate和annotate GROUP BY的使用方法
查看>>
TFS简介
查看>>
docker管理平台 shipyard安装
查看>>
安装django
查看>>
Bootstrap3 栅格系统-简介
查看>>
ADODB类库操作查询数据表
查看>>
【java】File的使用:将字符串写出到本地文件,大小0kb的原因
查看>>
安卓音乐播放器开发实例
查看>>
Junit指定测试执行顺序
查看>>
PHP put提交和获取数据
查看>>
some requirement checks failed
查看>>
存储管理
查看>>
HDU-2089-不要62
查看>>