JS中的闭包,其本质就是在函数内部再创建一个函数。
当外部函数执行环境被销毁后,内部函数的作用域链依然保持着对外部函数活动对象的引用,简而言之,闭包就是能够读取其他函数内部变量的函数。 我们可以将闭包理解为:
对于外部函数f1,内部函数f2,当f1终止后,f2任然对f1的AO保持访问。因此,可以总结出闭包的三个特性:
函数嵌套函数函数内部可以引用函数外部的参数和变量参数和变量不会被垃圾回收机制回收js的闭包与java略有不同,它有两种主要表现形式:
函数作为返回值 function fun1(){ var name = 'Tom'; return function(){ return name; } } var b = fun1(); console.log(b());//Tom当执行var b = fun1()时,返回的是function(){return name;}这个函数,再执行b()时,返回内部函数返回值name。 它的作用域链如下:
函数作为参数传递 var num = 100; var fn1 = function(x){ if(x > num)console.log(x); } function(fn2){ var num = 200; fn2(250) }(fn1)function(fn2)是一个自执行函数,将fn1函数传入它当作参数,在自执行函数中执行了传入的函数,所以fn1函数中的num = 200,而函数fn1的参数x = 250,所以执行if后面的输出语句得到结果250。 闭包的作用:
读取自身函数外部的变量——沿着作用域链去查找让外部变量始终保存在内存中我们来看一个例子:
function outer() { var result = new Array(); for (var i = 0; i < 2; i++) {//注:i是outer()的局部变量 result[i] = function () { return i; } } return result;//返回一个函数对象数组 //这个时候会初始化result.length个关于内部函数的作用域链的值 } var fn = outer(); console.log(fn[0]());//2 console.log(fn[1]());//2 fn[0]执行过程:可以看到result[0]函数的活动对象里并没有定义i这个变量,于是沿着作用域链去找i变量,结果在父函数outer的活动对象里找到变量i(值为2),而这个变量i是父函数执行结束后将最终值保存在内存里的结果;从这个步骤可以知道:js函数内的变量值不是在编译的时候就确定的,而是等在运行时期再去寻找的
那么,我们如何让result数组返回期望值呢? 我们知道,result的AO里有一个arguments,arguments对象是一个参数的集合,是用来保存对象的类数组。那么我们就可以把i当成参数传进去,这样一调用函数生成的活动对象内的arguments就有当前i的副本。
方法一 function outer() { var result = new Array(); for (var i = 0; i < 2; i++) { //定义一个带参函数 function argu(num) { return num; } //把i当成参数传进去 result[i] = argu(i); } return result; } var fn = outer(); console.log(fn[0]);//0 console.log(fn[1]);//1我们可以画出作用域链来理解方法一fn[0]的执行过程:
方法二: function outer() { var result = new Array(); for (var i = 0; i < 2; i++) { //定义一个带参函数 function arg(num) { function innerarg() { return num; } return innerarg; } //把i当成参数传进去 result[i] = arg(i); } return result; } var fn = outer(); console.log(fn[0]()); console.log(fn[1]());方法一虽然可以得到期望值但是会破坏闭包,方法二可以得到期望值并且不会破坏闭包,但是会让函数变得复杂,但是更推荐方法二。
向上面方法二这样,保证了闭包完整性,但函数较为复杂,写起来会比较困难,因此徐娅将其进行一些简化,简化后的方法二如下:
function outer() { var result = new Array(); for (var i = 0; i < 2; i++) { //定义一个带参函数 result[i] = function (num) { function innerarg() { return num; } return innerarg; }(i);//预先执行函数写法 //把i当成参数传进去 } return result; }