闭包的总结(干货1)

时间:2022-05-09 22:45:56

闭包:函数可以记住所在词法作用域,就产生了闭包,即使函数在当前词法作用域之外执行 ,闭包无处不在。。。请忽略这句话 ,看完在说

function foo() {
        var a = 1;
        function bibao() {
            console.log(a);
        }
        return bibao;
    }
    var baz = foo();
    baz();

bibao()能访问foo的内部作用域,然后bibao()本身作为值传递给baz,在foo执行之后,注意 baz=foo(),这里foo()会先运行,然后将返回值赋值给baz,然后运行baz(),实际是通过不同的标识符引用内部的函数bibao();bibao()可以正常的执行,实现了bibao()在自己定义时的词法作用域以外执行。foo执行完成之后,通常期待foo()整个内部空间被销毁,被垃圾回收器回收空间,但是,拜baz()所赐,baz能访问foo()作用域,使得该作用域一直纯在。

这个例子中,bibao对作用域的引用就叫闭包

 

再来看一个传递函数是间接的:

var fn;
    function foo() {
        var a=2;
        function bibao() {
            console.log(a)
        }
        fn=bibao;
    }
    function bar() {
        fn(); 
    }
    foo();
    bar();

同上面例子,不过这里外部函数运行,实现了全局变量fn对bibao()的引用,bibao()能访问foo内部词法作用域,在运行fn()的时候 ,就能实现了闭包

再比如:

function foo() {
var a = 2;
function baz() {
console.log( a ); // 2
}
bar( baz );
}
function bar(fn) {
fn(); // 闭包
}

运行foo()实现baz的引用绑定到bar作用域 ,bar是全局函数

总结:

无论通过何种手段将内部函数传递到所在的词法作用域以外,它都会持有对原始定义作用
域的引用,无论在何处执行这个函数都会使用闭包。

在比如我们经常写的函数:

function wait(message) {
        setTimeout( function timer() {
            console.log( message );
        }, 1000 );
    }
    wait( "Hello, closure!" );

这也是闭包

将一个内部函数(名为timer)传递给setTimeout(..)。timer 具有涵盖wait(..) 作用域的闭包,因此还保有对变量message 的引用。
wait(..) 执行1000 毫秒后,它的内部作用域并不会消失,timer 函数依然保有wait(..)作用域的闭包(也就是引用)。

function setupBot(name, selector) {
$( selector ).click( function activator() {
console.log( "Activating: " + name );
} );
}
setupBot( "Closure Bot 1", "#bot_1" );
setupBot( "Closure Bot 2", "#bot_2" );

同理:事件函数也是闭包,本质上只要用到回调函数的都是使用闭包


在比如:

for (var i=1; i<=5; i++) {
        setTimeout( function timer() {
            console.log( i );   //结果是输出5次6
        }, i*1000 );
     }   

显而易见的,延迟函数的回调会在循环结束时才执行,为什么会捕捉不到?

它们被封闭在一个共享的全局作用域中,因此实际上只有一个i

解决办法:延迟函数的回调重复定义五次,完全不使用循环,用匿名函数立即执行产生5个数作用域包裹对应变量i

for (var i=1; i<=5; i++) {
(function() {
setTimeout( function timer() {
console.log( i );
}, i*1000 );
})();
}

当然,你也可以直接用ES新特性:

for (let i=1; i<=5; i++) {
        setTimeout( function timer() {
            console.log( i );
        }, i*1000 );
    }

延迟函数的回调可以将新的作用域封闭在每个迭代内部,每个迭代中都会含有一个具有正确值的变量供我们访问

在比如:模块的例子,一个函数返回一个公用的对象接口

function CoolModule() {
var something = "cool";
var another = [1, 2, 3];
function doSomething() {
console.log( something );
}
function doAnother() {
console.log( another.join( " ! " ) );
}
return {
doSomething: doSomething,
doAnother: doAnother
};
}
var foo = CoolModule();
foo.doSomething(); // cool
foo.doAnother(); // 1 ! 2 ! 3

同理,调用CoolModule();实现了内部函数引用传递给外部词法作用域 ,foo就能访问CoolModule内部

1. 必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块
实例)。
2. 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并
且可以访问或者修改私有的状态。

实现单例模式

var foo = (function CoolModule() {
var something = "cool";
var another = [1, 2, 3];
function doSomething() {
console.log( something );
}
function doAnother() {
console.log( another.join( " ! " ) );
}
return {
doSomething: doSomething,
doAnother: doAnother
};
})();

 

大量代码例子出自,原谅我剽窃,实在是让人豁然开朗

《Javascript 权威指南》

《你不知道的javascript》