变量作用域和闭包

全局变量VS局部变量

全局变量的作用域是全局性的,即在整个JavaScript程序中,全局变量处处都在。

而在函数内部声明的变量,只在函数内部起作用。这些变量是局部变量,作用域是局部性的;函数的参数也是局部性的,只在函数内部起作用。

变量声明时如果不使用 var 关键字,那么它就是一个全局变量,即便它在函数内定义。如下图所示:


在函数a( )执行后,全局可以访问aa变量,而不可以访问bb变量。ps:在函数a执行前,变量aa也是没有定义的。

闭包

在 JavaScript 中,所有函数都能访问它们上一层的作用域。

JavaScript 支持嵌套函数。嵌套函数可以访问上一层的函数变量。

闭包是可访问上一层函数作用域里变量的函数,即便上一层函数已经关闭。---------w3c菜鸟教程

var add = (function () {    
    var counter = 0;    
    return function () {return counter += 1;}
})();

add();
add();
add();

上例:变量 add 指定了函数自我调用的返回字值。

自我调用函数只执行一次。设置计数器为 0。并返回函数表达式。

add变量可以作为一个函数使用。非常棒的部分是它可以访问函数上一层作用域的计数器(counter)。

这个叫作 JavaScript 闭包。

计数器受匿名函数的作用域保护,只能通过 add 方法修改。

counter变量被引用着所以不会被回收,它使得函数拥有私有变量变成可能。这是优点也是缺点,不必要的闭包只会增加内存消耗。


再接着看下面例子:

function makeFunc() {
    var name = "Mozilla";
    function displayName() {
        alert(name);
    }
    return displayName;
}
var myFunc = makeFunc();
myFunc();
在一些编程语言中,函数中的局部变量仅在函数的执行期间可用。一旦 makeFunc() 执行完毕,我们会认为 name 变量将不能被访问。然而, JavaScript 中,myFunc 是执行 makeFunc 时创建的 displayName 函数实例的引用,而 displayName 实例仍可访问其词法作用域中的变量,即可以访问到 name 
函数displayName 会形成 闭包 。 闭包是由函数以及创建该函数的词法环境组合而成。这个环境包含了这个闭包创建时所能访问的所有局部变量。

闭包之间不会相互影响,看下例:

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();
console.log(Counter1.value()); /* logs 0 */
Counter1.increment();
Counter1.increment();
console.log(Counter1.value()); /* logs 2 */
Counter1.decrement();
console.log(Counter1.value()); /* logs 1 */
console.log(Counter2.value()); /* logs 0 */
每个闭包都是引用自己词法作用域内的变量  privateCounter  。

每次调用其中一个计数器时,通过改变这个变量的值,会改变这个闭包的词法环境。然而在一个闭包内对变量的修改,不会影响到另外一个闭包中的变量
ps:以这种方式使用闭包,提供了许多与面向对象编程相关的好处 —— 特别是数据隐藏和封装


易错点来啦在循环中创建闭包

<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();
以上结果是什么呢?---- 无论焦点在哪个 input 上,显示的都是关于年龄的信息!!

why??? 

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

这明显不是我们想要的,解决这问题的一种方案是-----使用更多的闭包:

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

这段代码如我们所期望的那样:所有的回调不再共享同一个环境, makeHelpCallback 函数为每一个回调创建一个新的词法环境。在这些环境中,help 指向 helpText 数组中对应的字符串。

第二种方法使用了匿名闭包:

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++) {
    (function() {
       var item = helpText[i];
       document.getElementById(item.id).onfocus = function() {
         showHelp(item.help);
       }
    })(); // 马上把当前循环项的item与事件回调相关联起来
  }
}

setupHelp();
第三种方法:

避免使用过多的闭包,可以用let关键词

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++) {
    let item = helpText[i];
    document.getElementById(item.id).onfocus = function() {
      showHelp(item.help);
    }
  }
}

setupHelp();

这个例子使用let而不是var,因此每个闭包都绑定了块作用域的变量,这意味着不再需要额外的闭包。




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值