javascript闭包

来源:互联网 发布:淘宝淘客推广 编辑:程序博客网 时间:2024/06/11 06:18

【原文】http://www.mollypages.org/misc/jsclo.mp


1.好文章

  略.

2. 说明:嵌套函数

  • 嵌套函数就是定义在另一个函数内的函数。

§  

§ functionfoo()

§     {

§     function bar() { }

§     }

函数bar是无法从函数foo外部访问,比如像这样使用:foo.bar()

  • 内部函数能够被暴露到外部函数外面。

§  

§ functionfoo()

§     {

§     bar = function () { }

§     }

§     

§ foo(); //=> assigns the inner (anonymous) function to global variable bar

§ bar();  //=>invokes nested function bar

函数bar现在能够从函数foo外部访问,因为它被赋值给了一个全局变量bar(由于bar没有用var声明,所以它成了一个全局变量)。

注意,foo()至少被调用一次,这样变量bar才能够被赋值。

  • 内部函数也可以从外部函数返回。

§  

§ functionfoo()

§     {

§     var bar = function () {  }

§     return bar;

§     }

§     

§ varbar_reference = foo();

§ bar_reference();//=>invokes nested function bar

函数bar现在能够在函数foo外部访问到,因为它被赋值给一个全局变量bar_reference。

注意,foo()至少被调用一次来返回bar的引用。

3. 闭包

  • 正如上面所讲,嵌套函数能在外部函数之外被访问。闭包是在定义嵌套函数和return的时候被创建(或定义和调用)。

闭包简单来说就是内部函数能够访问外部函数内的变量(局部变量)。

很有趣的地方是,在外部函数已经执行完毕并不再存在,它还能够访问外部函数的那些局部变量。这是通过javascript内部把本地外部变量的状态保存在一个隔离的闭包变量中。

 

function foo()

    {

    var x = "hello";

    function bar() {

        alert(x);

        }

    return bar;

    }

   

var bar_ref = foo();

//foo has finished executing at this point and local

//variable x does not exist anymore

 

bar_ref();  //=> alerts "hello"

在这个例子中,bar_ref() 能正确地弹出”hello”。

因为变量x仍能通过闭包访问到。

  • 局部变量是通过在外部函数中利用关键字var和任何的方法参数保存在闭包中的。

·         

·        functionfoo(x)

·            {

·            function bar() {

·                alert(x);

·                }

·            return bar;

·            }

·         

·        varbar_ref = foo("hello");

·        bar_ref();

在这个例子中,bar_ref() 能正确地弹出"hello",因为局部方法的形式参数能够通过闭包被访问到。

  • 重要说明:在内部函数被赋值或返回的任何时候都会创建一个新的闭包。

 

function foo(x)

    {

    function bar() {

        alert(x);

        }

    return bar;

    }

 

var bar_ref_1 = foo("hello");

var bar_ref_2 = foo("world");

 

bar_ref_1();  //alerts hello

bar_ref_2();  //alerts world

说明:这意味通过同样的嵌套函数能创建多个闭包(每次嵌套函数被调用的时候创建新闭包),另外,修改一个闭包的变量不会影响另一个闭包的变量(译者注:闭包变量相互对立)。

  • 4. 闭包什么时候有用呢 ?

闭包基本上让我们摆脱了创建全局变量的需求。当事件/回调函数需要回调一些特定的数据时,闭包会发挥很大的作用。

在Java中,我们可以定义一个所谓叫回调函数的方法。我们能够创建多个不同的对象(每个都在不同的实例数据里),并且可以指定一个会被回调方法调用的特定对象。这种方法,在被调用时,将能够访问它自己的数据对象。

 

class MyObject {

    String greeting;

    MyObject(String word) {

        this.greeting = word;

        }

    void showGreeting() { System.out.print(greeting); }

    }

   

MyObject obj1 = new MyObject("hello");

MyObject obj2 = new MyObject("world");

 

SomeEventHandler.addCallBack(obj1); //invokes showGreeting() and prints "hello"

SomeEventHandler2.addCallBack(obj2); //invokes showGreeting() and prints "world"

说明,事件句柄是在不同的线程上运行的(通常是awt/swing事件句柄线程)但被调用的方法能够访问它自身的数据对象(因为所有的方法都跟对象绑定在一起)。

换个说法,(在Java里),它通常传递一个"this"对象指针给一个事件句柄(当一个事件句柄被对象添加到它自身时),这样事件句柄就能够使用"this"指针方法那个特定对象的数据。

传递给事件句柄的对象通常实现了一个接口,这样事件句柄(和编译器)就能够知道该对象具有一个众所周知的回调方法。(在这个例子中的showGreeting())


相比之下,现在在Javascript中,开始流行这个:

 

var greeting = "hello";

function foo() {

    alert(greeting);

    }

 

SomeElement.addEventHandler("onclick", foo);

SomeElement2.addEventHandler("onclick", foo);

事件句柄将调用全局函数(仅能够访问全局变量)。在这里例子,我们仅能使用一个greeting变量,每次它都是从foo()读取(因为它是一个全局变量)。

如果我们现在试着按照Java的模型和对象化(object-ify),我们可以说:

 

function MyObject(word) {

    this.greeting = word;

    this.handle = function(){

        alert(this.greeting);

        }

    }

 

var obj1 = new MyObject("hello");

var obj2 = new MyObject("world");

 

SomeElement.addEventHandler("onclick", obj1);

SomeElement2.addEventHandler("onclick", obj2);

这可能有能行,或不行。这取决于它是怎样实现回调的:

回调机制能把回调方法作为一个函数来调用。如果嵌套调用 obj1.handle()这个事件句柄,那’this’将不能指向正确的对象(它将指向全局变量即window,而不是obj1)

这种回调方式甚至可能不能接收一个对象,它可能仅仅能接收一个函数。这个在javascript中非常普遍。例如,setTimeout只能接受一个函数。

1.SomeElement.addEventHandler("onclick",obj1, some_function);

在这里情况,访问一个函数中的特定数据将是不能(除非是一个全局变量或一个闭包)。

那闭包是怎么解决这问题呢?

 

function remember_me(word) {

    function inner() {

        alert(word);

        }

    return inner;

    }

SomeElement.addEventHandler("onclick", remember_me("hello"));

在这个例子,函数和相关的数据一起被记录在一个闭包中。事件句柄使用一个函数做回调函数(不是一个对象)。然而,这个函数能够访问它自身独立的数据,每个闭包函数都有其单独的数据。

在概念上跟有一个基于回调访问自身对象数据的方法相似。

我感觉闭包一般趋向于让事情变得更复杂和难以理解。它们不该是一种编程语言的特性,仅是困惑的源头(在javascript,基于上面的一些原因闭包有时是必要的)。

5. 浏览器特定的hacks和bugs

IE有一个垃圾回收bugs。这些不是闭包特有的,但在使用闭包的时候很容易会暴露。

正如上面的图所展示的,IE(7和更早版本)分开回收本地javascript和映射为javascript对象的HTML DOM的垃圾。

如果在HTML DOM对象和一个本地对象有一个环形链接,然后在IE浏览器的会话周期中都不会被作为垃圾回收(即使用从页面跳转到别的地方)。

 

 

function addHandler()

    {

    var mydiv =document.getElementById("myid");    

    mydiv.onclick = function() {

        alert(this.innerHTML +"\n");

    }

在这个例子,mydiv通过它的onclick事件句柄指向本地JS代码。

事件句柄的js代码通过内部产生的闭包重指向回mydiv 。这个环形引用导致了在IE中的内存泄露。

解决此问题的方法:

 

function addHandler()

    {

    var mydiv =document.getElementById("myid");    

    mydiv.onclick = handler;

    }

function handler() {

        alert(this.innerHTML +"\n");

        }

因为handler函数不是一个嵌套函数,所以没有闭包产生。

另一个中没有闭包的解决方法:

 

function addHandler()

    {

    var mydiv =document.getElementById("myid");    

    mydiv.onclick = new Function(

                     'alert(this.innerHTML)'

                       );

        }

尽管句柄函数是被内联定义,但函数构造函数的使用阻止了一个隐式的闭包(通过构造函数创建的函数不会保存局部作用域)。

      

译者注:初次翻译,翻译得不对或不完善的,欢迎拍砖。

 


0 0
原创粉丝点击