您的位置:首页 > Web前端 > JavaScript

JavaScript之闭包问题以及立即执行函数

2017-04-11 16:45 405 查看
今天我将会来浅谈一下关于JavaScript的立即执行函数以及闭包问题。
首先我们先要了解一下关于立即执行函数:

( function(){…} )()( function (){…} () )是两种javascript立即执行函数的常见写法,最初我以为是一个括号包裹匿名函数,再在后面加个括号调用函数,最后达到函数定义后立即执行的目的,后来发现加括号的原因并非如此。要理解立即执行函数,需要先理解一些函数的基本概念。

函数声明、函数表达式、匿名函数


函数声明:function fnName () {…};
使用function关键字声明一个函数,再指定一个函数名,叫函数声明。




函数表达式 var fnName = function (){…};
使用function关键字声明一个函数,但未给函数命名,最后将匿名函数赋予一个变量,叫函数表达式,这是最常见的函数表达式语法形式。




匿名函数:function () {};
使用function关键字声明一个函数,但未给函数命名,所以叫匿名函数,匿名函数属于函数表达式,匿名函数有很多作用,赋予一个变量则创建函数,赋予一个事件则成为事件处理程序或创建闭包等等。


函数声明和函数表达式不同之处在于:

一、Javascript引擎在解析javascript代码时会‘函数声明升’(Function declaration Hoisting)当前执行环境(作用域)上的函数声明,而函数表达式必须等到Javascirtp引擎执行到它所在行时,才会从上而下一行一行地解析函数表达式;

二、函数表达式后面可以加括号立即调用该函数,函数声明不可以,只能以fnName()形式调用 。以下是两者差别的两个例子。

fnName();
function fnName(){
...
}
//正常,因为‘提升'了函数声明,函数调用可在函数声明之前

fnName();
var fnName=function(){
...
}
//报错,变量fnName还未保存对函数的引用,函数调用必须在函数表达式之后


在理解了一些函数基本概念后,回头看看( function(){…} )()和( function (){…} () )这两种立即执行函数的写法,最初我以为是一个括号包裹匿名函数,并后面加个括号立即调用函数,当时不知道为什么要加括号,后来明白,要在函数体后面加括号就能立即调用,则这个函数必须是函数表达式,不能是函数声明。

(function(a){
console.log(a);   //firebug输出123,使用()运算符
})(123);


闭包:
闭包,简单来说就是函数嵌套函数,或者说定义在一个函数内部的函数,它是将函数内部和函数外部连接起来的一座桥梁。

闭包可以用在许多地方。它的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。

例1:请看下面代码:

//闭包概念:函数嵌套函数
function t2() {
var b=100;
function t3() {
console.log(b);
}
return t3();
}
t2();




运行结果为:



在这段代码中,在函数t2内部声明的变量b本来是一个局部变量,为什么在调用时t3函数能打印出b变量的值呢?原因如下:

在上面的代码中,函数t3就被包括在函数t2内部,这时t2内部的所有局部变量,对t3都是可见的。但是反过来就不行,t3内部的局部变量,对t2就是不可见的。这就是Javascript语言特有的”链式作用域”结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。

这就是闭包的其中一个作用,可以读取函数内部的一个变量。

例2:请看下面代码:

function f1(){
    var n=999;
    nAdd=function(){n+=1}
    function f2(){
      alert(n);
    }
    return f2;
  }
  var result=f1();
  result(); // 999
  nAdd();
  result(); // 1000

在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。

为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

这段代码中另一个值得注意的地方,就是”nAdd=function(){n+=1}”这一行,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。

为了深入理解以上所讲内容,请看以下代码段:

// 这个代码是错误的,因为变量i从来就没被locked住
// 相反,当循环执行以后,我们在点击的时候i 才获得数值
// 因为这个时候i操真正获得值
// 所以说无论点击那个连接,最终显示的都是I am link #10(如果有10个a元素的话)

var elems = document.getElementsByTagName('a');

for (var i = 0; i < elems.length; i++) {

elems[i].addEventListener('click', function (e) {
e.preventDefault();
alert('I am link #' + i);
}, 'false');

}

// 这个是可以用的,因为他在自执行函数表达式闭包内部
// i的值作为locked的索引存在,在循环执行结束以后,尽管最后i的值变成了a元素总数(例如10)
// 但闭包内部的lockedInIndex值是没有改变,因为他已经执行完毕了
// 所以当点击连接的时候,结果是正确的

var elems = document.getElementsByTagName('a');

for (var i = 0; i < elems.length; i++) {

(function (lockedInIndex) {

elems[i].addEventListener('click', function (e) {
e.preventDefault();
alert('I am link #' + lockedInIndex);
}, 'false');

})(i);

}


var elem = document.getElementsByTagName('div'); // 如果页面上有5个div

for(var i = 0; i < elem.length; i++) {
elem[i].onclick = function () {
alert(i); // 总是5
};
}

上方是一个很常见闭包问题,点击任何div弹出的值总是5,因为当你触发点击事件的时候i的值早已是5,可以用下面方式解决:

var elem = document.getElementsByTagName('div'); // 如果页面上有5个div

for(var i = 0; i < elem.length; i++) {
(function (w) {
elem[w].onclick = function () {
alert(w); // 依次为0,1,2,3,4
};
})(i);
}


转自csdn:http://blog.csdn.net/sinat_35512245/article/details/53514804

部分代码参考:阮一峰的网络日志
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: