全局变量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
,因此每个闭包都绑定了块作用域的变量,这意味着不再需要额外的闭包。