作用域闭包

本文详细解释了JavaScript中闭包的概念及其应用场景,包括定时器、事件监听器等,并介绍了如何利用闭包实现模块模式,同时提供了现代模块机制的示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

闭包是基于词法作用域书写代码产生的自然结果
当函数可以记住并访问所在的此法作用域的时候,就产生了闭包

 var  a=2;
    (function () {
        console.log(a);
    })()

由上面的定义就知道,上面的代码并不是严格意义上的闭包。因为并没有在函数本身创建的此法作用域以外执行。
。也就是当前词法作用域之外执行。

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

但是上面的这个不是严格意义上的闭包,下面这个程序是严格意义上的闭包

   function  foo() {
        var a=2;
        function bar() {
            console.log(a)
        }
      return bar;
    }
    var baz=foo();
    baz();

一个实际意义上的完整的闭包。
bar()的此法作用域能够访问foo() 的内部作用域。然后将bar()函数本身当作一个值进行传递。也就是在这个例子中,bar所引用的函数对象本身当作返回值。所以bar在自己定义的词法作用域以外的地方被执行。
闭包的缺点:
在foo执行之后,我们通常会期待foo的整个作用域都被销毁。但是实际上并没有。foo()的内部作用域依然存在,因此没有被 回收。bar()本身还在使用这个作用域。这个作用域会一直存活,以供bar()在任何时间被调用。
闭包:bar()依然持有该作用域的的引用,这个引用就是闭包。
简单的说闭包就是那个会一直存在的作用域。。

闭包的其他出现的形式;

函数类型作为参数进行传递
无论使用何种方式对函数的值进行传递,当函数在别出被调用时可以观察到闭包。

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

所以闭包在定时器、事件监听器、AjAx请求,跨窗口通信,web workers或者任何异步或者同步的任务中,只要使用回调函数,实际上就是在使用闭包。

循环和闭包

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

运行的结果是每隔一秒就会输出一个6.
原因:延迟函数的回调会在循环结束的时候才执行。尽管循环中的5个函数是在各个迭代中分别定义的,但是他们被封闭在一个共享的全局作用域中,因此实际上只有一个i。
解决办法是:每次循环的时候都要分别为自己保留一个i的副本。

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

以上代码就实现了每隔一秒就输出0,1,2,3,4,5

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

    }

以上代码就实现了每隔一秒就输出0,1,2,3,4,5,使用let关键字让每一次循环都新生成一个作用域,执行延迟函数。

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

以上代码就实现了每隔一秒就输出0,1,2,3,4,5。变量在循环的过程中不止被声明一次每次迭代都会声明。随后的每个迭代都会使用上一个迭代结束时的值来初始化这个变量。

模块

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 module=new CoolModule();
    module.doSomething();
    module.doAnother();

这种模式就被称为模块。CoolModule这个名字一定要起得非常有意义和独特。
分析上面的这种模块的模式:
首先:CoolModule()只是一个函数,必须要通过调用它创建一个模块的实例。如果不执行外部函数,内部作用域和闭包都无法创建。
其次:CoolModule()返回一个用对象字面量的语法{key:value}来表示对象。这个返回的对象中含有对内部函数而不是内部数据变量的引用。这样内部的数据还是处于隐藏的状态。可以将这个对象类型的返回值看作本质上是模块的公共API。(一个很好的例子就是jquery)

所以模块模式需要两个必要的条件:

1、必须有外部的封闭函数(CoolModule),该函数必须至少被调用一次(每次创建一个新的模块的时候,都会创建一个新的模块实例)
2、封闭函数至少要返回一个内部函数,这样内部函数才能在私有作用域中形成闭包。
注意:
模块也是普通的函数,因此可以接收参数

 function CoolModule(id) {
       function identify() {
           console.log(id);
       }
        return {
            identify:identify
        }
    }
    var cc1=new CoolModule('sa');   
    cc1.identify();   //sa
    var cc2=new CoolModule('ffdfd');
    cc2.identify();  //ffdfd

模块模式的另一个强大的用法:

命名将要作为公共API返回的对象

function CoolModule(id) {
        function change() {
            PublicAPI.identify=identify2
        }
        function identify1() {
            console.log(id);
        }
       function identify2() {
           console.log(id.toUpperCase());
       }
        var PublicAPI={    //命名将要作为公共API返回的对象
            identify:identify1,
            change:change
        }
        return PublicAPI;
    }
    var cc=new CoolModule("sasas");
    cc.identify();    //sasas
  cc.change();
    cc.identify();   //SASAS

也就是给模块的返回值起了一个名字,这样的话就可以修改。

现代模块机制

 var Mymodules=(function Manager() {
        var modules={};
        function define(name,deps,impl) {  //deps为依赖的模块
            for(var i=0;i<deps.length;i++){
                   deps[i]=modules[deps[i]];
            }
            modules[name]=impl.apply(impl,deps);
        }
        function get(name) {
            return modules[name];
        }
        return {
            define:define,
            get:get
        }
    })();
    Mymodules.define("bar",[],function () {
        function hello(who) {
            return "Let me introduce   " +who;
        }
        return {hello:hello};
    });
    Mymodules.define("test",[],function () {
        function testConsole() {
            return "we are testing";
        }
        return {testConsole:testConsole};
    });
    Mymodules.define("foo",["bar","test"],function (bar) {
        function awesome() {
            var cc="dsdasa";
              console.log(test.testConsole().toUpperCase())
              console.log(bar.hello("cc").toUpperCase())
        }
        return {
            awesome:awesome
        }
    });
    var test=Mymodules.get("test");
    var bar=Mymodules.get("bar");
    var foo=Mymodules.get("foo");
    foo.awesome();
    console.log(bar.hello("hippo"));
    console.log(2121);

块作用域的替代方案

在ES3发布以来,js中就有了块作用域,with和catch语句就是块作用域的两个例子,但是随着ES6的let语句的引入,我们的代码终于有了创建完整、不受约束的块作用域的能力。

   {
        let a=3;
        console.log(a);   //3
    }
    console.log(a);  //a is not defined

let让程序形成了一个块级作用域。

   {
        var a=3;
        console.log(a);   //3
    }
    console.log(a);  // 3

var 就不可以形成一个块级作用域。
以上是ES6解决块级作用域的方案,但是在ES6之前怎样解决块级作用域?

 try{
        throw 2;
    }catch (a){
        console.log(a);  //2
    }
    console.log(a);// a is not defined

总结

当函数可以记住并访问所在的此法作用域,即使函数在当前的词法作用域外执行,这时就产生了闭包。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值