第五章.闭包 (5.Closing in on closures)[完成]

这章写得极为精彩,让我明白了好多以前根本不知道的概念。

闭包是一个神奇的东西,新果说过,闭包就是可以当作鞭子来用,可以打出鞭子头一样的力道。

所谓的函数化这个概念中,闭包是一个很重要并且很精髓的概念。

想要理解函数化编程,就要深刻的理解闭包。

本章重点:

1.闭包的定义,闭包是什么,闭包如何工作

2.利用闭包来实现一些简单的开发

3.利用闭包实现性能上的增强

4.利用闭包实现私有域

5.1 闭包如何工作(How closures work) #

问:闭包是什么?

答:简单来说,closure是一块域,这块域是由创建一个function而来,这个function可以访问和操作它的外部的变量

(Simply put, a closure is the scope created when a function is declared that allows the function to access and manipulate variables that are external to that function.)

这个概念最好还是用代码来解释,所以让我们看看5.1这个例子:

Listing 5.1: A simple closuer

var outerValue = 'ninja';  
function outerFunction(){  
    assert(outerValue == 'ninja', "I can see the ninja")  
}  
outerFunction(); 

你可能写过n多次这样的代码,可你竟然没有意识到你在创建闭包(closure)!

你不相信?估计是因为你没有感觉到有任何的惊喜。

因为out value和outer function的作用域是全局域(global scope),而全局域是永远不会消失(自从这个页面被加载就存在了),

另外就算是这个function可以访问outer value,这个特点也没有实用的价值。

所以就算closure已经存在了,它的用处也不明显。

让我们给他加上一点料,来看看5.2这个列子吧:

Listing 5.2: A not-so-simple closue

var outerValue = 'ninja';  
var later;  

function outerFunction(){  
    var innerValue = 'samurai';  

    function innerFunction(){  
        assert(outerValue, "I can see the ninja");  
        assert(innerValue, "I can see the samurai");  
    }  

    later = innerFunction;  
}  

outerFunction();  
later();  

首先,我们定义了later这个变量,我们会在后面用到它。

然后,我们在outer function中定义了inside这个变量,这样inside就被限制在outerFunction域内了。

然后,我们在outerFunction中定义了一个innerFunction,innerFunction可以访问我们已经定义过的所有外部变量。

是的!我们可以这样做!记住上一章我们讨论过的吗?

function是自然类型的对象(first-class objects),它和变量一样在任何地方都可以被创建。

我们当然也可以将inner function赋给later变量,以便稍后调用。

一切就绪,我们开始运行。

我们调用outer function,创建了inner function,然后inner function赋给了later变量(全局变量),然后通过调用全局变量later,等价于调用了inner function。

发生了什么事情?

1.inner function可以访问outerValue,因为outerValue属于全局域,

2.由于js的特性,在正常情况下,全局域中我们是无法访问到innerValue的,但是通过将innerFunction赋给later变量,later就称为了域之间的桥梁,我们自然在全局域可以访问到innerValue。

这是什么原理呢?答案就是,闭包(closures)!

当我们创建innerFunction的时候,不仅仅是定义了一个function,我们还创建了一个域。

在外部掉用innerFUnction的时候,虽然调用发生在全局域,但是innerFunction还是会访问当初被定义的时刻的那个原始域。这个域就是它的闭包(closure)

让我们来看看下一个例子:5.3

Listing 5.3: What else closures can see

var outerValue = 'ninja';  
var later;  

function outerFunction()  
{  
    var innerValue = 'samuai';  

    function innerFunction(paramValue)  
    {  
        assert(outerValue, "Inner can see the ninja");  
        assert(innerValue, "Inner can see the samurai");  
        assert(paramValue, "Inner can see the wakizashi");  
        assert(tooLate, "Inner can see the ronin");  
    }  

    later = innerFunction;        
}  

assert(!tooLate, "Outer can't see the ronin");  

var tooLate = 'ronin';  

outerFunction();  
later('wakizashi');  

这个例子中表达了3个关于closure的概念:

1.function的参数也被包含在闭包之中

2.所有的外部变量都属于闭包可访问的范围(外部域),包括那些在闭包后面创建出来的变量。

3.在同一个域中,不可以访问后面才创建的变量

第2和第3点解释了为什么只有inner closure可以看见tooLate,而outer closure则不可以看见tooLate。

虽然闭包并不引人注意(你并不能通过工具检测到“闭包”这个对象),但是我门还是要意识到闭包其实是占用了内存的。

请记住,闭包威力无穷,但是也有危险。

如果你有强烈的意愿想用好闭包,闭包毫无疑问是威力无穷的。

但是它理所当然需要系统开销。

所有闭包中的信息数据都已经缓存在内存之中,

如何释放掉这块内存呢?

这里有两种办法:

1.通过JavaScript引擎的GC垃圾回收机制

2.刷新或者关闭当前页面。

5.2 让闭包用起来(Putting closures to work) #

现在,我们已经明白了闭包是什么,并且知道它如何使用(虽然是很high level的),

现在我们要真正让闭包用起来。

5.2.1 私有变量(Private variables) #

在JavaScript中如何实现让一个域拥有私有的变量呢?

答案就是使用闭包。

我们看到大部分JavaScript菜鸟都是在用面向对象(Object-oriented)的方式来编写JavaScript,

这样做带来的缺点就是无法实现私有变量。

在例子5.4中我们会看到闭包是如何搞定私有变量的

Listing 5.4:Using closures to approxiate private variables

function Ninja()  
{  
    var slices = 0;  

    this.getSlices = function()  
    {  
        return slices;  
    }  

    this.slice = function()  
    {  
        slices++;  
    }  
}  

var ninja = new Ninja();  

ninja.slice();  

assert(ninja.getSlices() == 1,  
    "We're able to access the internal slice data.");  
assert(ninja.slices == undefined,  
    "And the private data is inaccessible to us.");  

这个例子表明了,Ninja这个函数中的数据状态,是由Ninja这个函数来维护,

外部是无法干预到Ninja这个函数内部的变量的。

函数内部的变量,只允许Ninja的内部方法来访问。

现在,让我们回过神来,我们要继续探索闭包的另一个牛逼的用法。

5.2.2 Callbacks and timers #

另一个闭包的牛逼用法,就是实现了回调(callbacks)和定时器(timer)

让我们来看一个利用jQuery来实现Ajax请求的例子5.5

Listing 5.5: Using closures from a callback for an Ajax request

var jQuery = function(){  
    return {  
        click:function(){}  
    }  
};  

jQuery('#testButton').click(function(){  
    var elem$ = jQuery("div");  
    elem$.heml("Loading...");  

    jQuery.ajax({  
        url: test.html,  
        success: function(html){  
            assert(elem$,  
                "We can see elem$, via the closure for this callback.");  
            // elem$.html(html);  
        }  
    })  
})  

jQuery大家制定用的很熟悉了,这里就不过多解释了,如果你看不懂这个例子,先去http://www.w3cschool.cn/index-30.html看看jQuery的概念吧,我只能帮你到这里了Brother。

下面让我们看看Timer的例子:

Listing 5.6: Using a closure in a timer interval callback

var elem = document.getElementById("box");  
var tick = 0;  

var timer = setInterval(function(){  
    if (tick < 100){  
        elem.style.left = elem.style.top = tick + "px";  
        tick++;  
    }else{  
        clearInterval(timer);  
        assert(tick == 100,   
            "Tick accessed via a closure.");  
        assert(elem,  
            "Element also acessed via a closure.");  
        assert(timer,  
            "Timer reference also obtained via a closure.")  
    }  
}, 10); 

下面,让我们来看看如何让function的上下文(contexts)为我们工作。

5.3 Binding function contexts #

我们在上一章已经讨论果函数上下文这个概念,你还记得如何更改一个函数的上下文吗?

对了!用.call()和.apply()。

ps:你还记得call和apply的区别吗?不记得的话就回去看看吧。

在下面这个例子中,我们想让一个对象的方法绑定到一个dom元素上

Listing 5.7:Bingding a specific context to a function

var button = {  
    clicked : false,  
    click: function(){  
        assert(this == elem, "This is the elem, not the object button")  
        this.clicked = true;  
        assert(button.clicked, "The button has been clicked");  
    }  
}  

var elem = document.getElementById("test 5.7");  
elem.addEventListener("click", button.click, false);  

运行上面这个例子之后,我们会发现,结果并不是我们预期的。

原因是click方法并没有向我们预期的那样绑定在了button对象上。

根据我们之前学习的第三章的知识,如果我们这么调用:

button.click()

那么函数的的上下文就会是button对象了。

但是在这个例子中,函数的上下文则是dom中的

 
53
Kudos
 
53
Kudos

Now read this

第六章.原型与面向对象(6.Object-orientation with prototypes)[完成]

翻译 Secrets of the JavaScript Ninja (JavaScript忍者禁术) 第六章.原型与面向对象(6.Object-orientation with prototypes) 本章重点: 1.利用函数实现构造器 2.解释prototyes 3.利用prototypes实现对象的扩展 4.avoiding common gotchas 5.利用inheritace构建classes... Continue →