作用域闭包

来源:互联网 发布:排序算法比较次数 编辑:程序博客网 时间:2024/05/23 14:24

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

 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

总结

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

0 0
原创粉丝点击