作用域

在JavaScript中变量的作用域与其他语言不同,JavaScript的作用域不是由{}来界定,而是函数。所以循环实际上是在全局作用域中

1
2
for(var i = 0; i < 10; i++) {}
console.log(i); // 10

全局变量&局部变量

JavaScript变量的作用域分为两种,局部全局
在JavaScript中声明局部变量有两种方式,一种是在全局变量环境下使用var声明(显式声明的全局变量),另一种是在任何地方直接初始化变量,那么它也是全局变量(隐式声明的全局变量)。

1
2
3
4
5
var name = 'Cherryl';   // 全局变量(显式)
var age;
function f1() {
age = '20'; // 还是全局变量(隐式)
}

隐式声明的全部变量和显式声明的全部变量的区别:

隐式声明的全局变量可以删除,显式声明的全部变量就无法删除。
因为通过var声明的式显式全局变量,其实际上是为window对象增加了一个不可配置的属性,而不加var声明的隐式全局变量,其实际上是为window对象增加了一个可以配置的属性。

影响:这样的设计,使得声明赋值`很容易混淆,造成全局变量的滥用,使许多bug源头之一。

除了在任何地方直接初始化声明全局变量这一特殊之处外,JavaScript全局变量还可以在函数内部直接读取

1
2
3
4
5
var name = 'Cherryl';
function f1() {
console.log(name);
}
f1(); // 'Cherryl'

在函数中使用var定义的变量为局部变量。因为JavaScript的作用域是由函数界定,那么理所因当的函数外部是无法读取函数内部的局部变量。这一点其实其它的变成语言也是这样的。

1
2
3
4
function f1() {
var name = 'Cherryl';
}
alert(name) // name is not defined

作用域链

作用域链的原理和原型链很类似。在某个环境中为了读取变量时,会沿着作用域来搜索这个变量,从作用域的前端开始,向上级搜索。如果在当前局部环境中没有找到该变量,则继续沿作用域向上搜索,直到最顶层。搜索到该变量时将停止搜索,如果最后还是没有找到该变量,那么意味着这个变量是未定义的,即它的值为undefined

1
2
3
4
5
6
var a = 'a';
function f1() {
var b = 'b';
console.log(a + b);
}
f1() // 'ab'

在这个例子中,在全局环境中定义了全局变量a,然后在函数中定义了局部变量b。首先在当前的局部环境中搜索变量a的值,没有找到。那么继续向上一级搜索,在全局环境中找到标识符a的变量的值’a’。接着在全局环境中搜索变量b,得到局部变量b的值’b’。最后输出变量a和b的值拼接后的字符串的值。


闭包

函数外部无法读取到函数内部定义的局部变量,所以当我们需要读取局部变量时据需要使用到闭包。

那么闭包是什么呢?我理解是函数返回一个局部作用域来使得函数外部能够读取函数内部的变量。因为JavaScript中作用域的界定是由函数来完成的,所以实际上也就是在函数中再返回一个函数。所以,在本质上,闭包就是将函数内部和外部连接起来的一座桥梁。

闭包的作用主要有两个:一个读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。

一般情况下,当函数执行后,函数执行后,函数所在的局部环境将被销毁,也就是说函数在执行后函数中的变量是会被销毁的,在内存中就仅存在全局变量,即全局变量。

但是使用闭包的情况又有所不同,在函数内部定义的匿名函数会包含函数(外部函数)中的变量,在外部函数执行完毕后,原本应该被销毁的局部变量不会被销毁,因为闭包的作用域链仍然存在引用这些局部函数,内存的垃圾回收机制不会回收这部分变量所在的内存空间。直到匿名函数被销毁后,这些局部对象才会被销毁。

1
2
3
4
5
6
7
8
9
10
11
12
  function f1(){  
    var n=999;  
    nAdd = function(){n+=1};  
function f2() {
alert(n)
}
return f2
  }  
  var result=f1();  
  result(); // 999  
  nAdd();  
  result(); // 1000

内存泄漏

由于闭包使得函数中的局部变量不会被垃圾回收机制回收,会依然存在于内存中,所以使用闭包的内存消耗很大,所以大量的使用闭包会造成性能问题,另外,在IE中可能会导致内存泄漏。解决方法是在退出函数前将不使用的局部变量全部删除。

1
2
3
4
5
6
7
8
9
10
function f1() {
var e = document.getElementById('id');
var id = e.id;

e.onclick = function() {
alert(id);
}

e = null; // 删除不使用的局部变量值,只将需要的 id 保存为副本
}

改变父函数内部变量的值

闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的私有属性(private value),这时一定要当心,不要随便改变父函数内部变量的值。


参考: