4

闭包是真的让人头晕啊,看了很久还是觉得很模糊。只能把目前自己的一些理解先写下来,这其中必定包含着一些错误,待日后有更深刻的理解时再作更改。

吐槽一下,闭包这个词的翻译真是有很大的误解性啊……

要说闭包,要先说下词法作用域。

词法作用域

简单来说,词法作用域就是定义在词法阶段的作用域。换句话说,词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的。

无论函数在哪里被调用,也无论它如何被调用,它的词法作用域都只由函数被声明时所处的位置决定。

闭包

关于闭包

闭包的各个理解:
当函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域之外执行,这时就产生了闭包。
作者:Cat Chen
闭包就是由函数创造的一个词法作用域,里面创建的变量被引用后,可以在这个词法环境之外自由使用。
闭包通常用来创建内部变量,使得这些变量不能被外部随意修改,同时又可以通过指定的函数接口来操作。
function foo(){
    var a=2;
    function bar(){
        console.log(a);
    }
    return bar;
}
var baz=foo();
baz();//2——闭包

两个作用:
(1):通过闭包,在外部环境访问内部环境的变量。
(2):使得这些变量一直保存在内存中,不会被垃圾回收。

上面的代码例子中,函数在定义时的词法作用域以外的地方被调用。闭包使得函数可以继续访问定义时的词法作用域。//?

调用的方式不仅仅只有以上代码中的通过不同的标识符引用调用其内部的函数,

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

循环和闭包

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

正常预想下,上面这段代码我们以为是分别输出数字1-5,每秒一个。
但实际上,运行时输出的却是每秒输出一个6,一共五次。

原因是,延迟函数的回调会在循环结束时才执行。
根据作用域的工作原理,循环中的五个函数是在各个迭代中分别定义的,但是它们都被封闭在一个共享的全局作用域中,实际上只有一个i。

我们可以通过IIFE创建作用域。(IIFE会通过声明并立即执行一个函数来创建作用域)。

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

但是这样创建的作用域里是空的,需要有自己的变量:

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

改进得到:

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

ES6引入的let在循环中不止会被声明一次,在每次迭代都会声明:

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

应用场景:模块

模块也是利用了闭包的一个强大的代码模式。

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

var foo=CoolMOdule();
foo.doSomething();//cool
foo.doAnother();//1!2!3
模块有2个主要特征:
(1):为创建内部作用域而调用了一个包装函数;
(2):包装函数的返回值必须至少包括一个对内部函数的引用,这样就会创建涵盖整个包装函数内部作用域的闭包。

关于模块的引入

import可以将一个模块中的一个或多个API导入到当前作用域中,并分别绑定在一个变量上;
module会将整个模块的API导入并绑定到一个变量上;
export会将当前模块的一个标识符导出为公共API。

Teemo
38 声望1 粉丝