闭包的理解

    技术2022-07-11  80

    JS闭包理解及使用

    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; }

    闭包的好处:

    保护函数内部变量安全,实现封装,防止变量流入其他作用域维持变量,可用于做缓存使用闭包时用匿名函数可以减少内存消耗

    闭包的弊端:

    变量不销毁,增加了内存消耗,严重可能造成内存泄漏——解决方案:使用完该变量后删除或者赋值null闭包涉及跨作用域访问,如果使用过多,会造成性能下降——解决方案:将需要跨作用域访问的变量保存在局部变量中,每次访问时直接使用局部变量

    使用闭包时请注意:

    由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除或者赋值为null;闭包会在外部函数的外面,改变外部函数内部变量的值。所以,如果你把外部函数当作对象使用,把闭包当作它的公用方法,把内部变量当作它的私有属性,一定不要随便改变父函数内部变量的值!
    Processed: 0.012, SQL: 9