码迷,mamicode.com
首页 > 其他好文 > 详细

(三)闭包和高阶函数

时间:2016-01-10 00:15:00      阅读:304      评论:0      收藏:0      [点我收藏+]

标签:

虽然javascript是一门面向对象的编程语言,但这门语言同时也同时拥有许多函数式语言的特性。

函数式语言的鼻祖是LISP,javascript设计之初参考了LISP两大方言之一的Schenme,引入了Lambda表达式,闭包,高阶函数等特性。使用这些特性,我们就可以灵活的编写javascript代码。

一:闭包

对于javascript程序员来说,闭包(closure)是一个难懂又必须征服的概念。闭包的形成与变量作用域以及变量的声明周期密切相关。

1.变量作用域

变量的作用域就是指变量的有效范围,我们最常谈到的是在函数中声明的变量作用域。

当在函数中声明一个变量时,如果没有使用var关键字,这个变量就会变成全局变量(当然这是一种容易造成命名冲突的做法。)

另外一种情况是用var关键字在函数中声明变量,这时候的变量即局部变量,只有在函数内部才能访问到这变量,在函数外面是访问不到的,代码如下:

var func = function() {
    var a = 1;
    console.log(a)
}
func()
console.log(a);//Uncaught ReferenceError: a is not defined

下面这段包含了嵌套函数的代码,也许能帮助我们加深对遍历搜索过程中的理解

var a = 1;
var func = function() {
    var b = 2;
    var func2 = function(){
        var c = 3;
        console.log(b);
        console.log(a)
    }
    func2()
    console.log(c) //Uncaught ReferenceError: c is not defined
}
func()

2.变量的生成周期

var func = function(){
    var a =1;
    console.log(a) //退出函数后局部变量a将销毁
}
func()
var func2 = function(){
    var a = 2;
    return function() {
        a++;
        console.log(a)
    }
}
var f = func2();
f() //3
f() //4
f() //5
f() //6

func2根我们之前的推论相反,当退出函数后,局部变量a并没有消失,而是停留在某个地方。这是因为,当执行 var f = func2()时,f返回了一个匿名函数的引用,它可以访问到func()被调用时的所产生的环境,而局部变量a一直处在这个环境里。既然局部变量所在的环境还能被外界访问,这个局部的变量就有了不被销毁的理由。在这里产生了一个闭包环境,局部变量看起来被延续了。

利用闭包我们可以完成很多奇妙的工作,下面介绍一个闭包的经典应用。

假设页面上有5个div节点,我们通过循环给div绑定onclick,按照索引顺序,点击第一个时弹出0,第二个输出2,依次类推。

<div>div1</div>
<div>div2</div>
<div>div3</div>
<div>div4</div>
<div>div5</div>
<div>div6</div>

<script type="text/javascript">
var nodes = document.getElementsByTagName(‘div‘)
console.log(nodes.length)
for (var i = 0; i < nodes.length; i++) {
    nodes[i].onclick = function() {
        console.log(i)
    }
}
</script>

在这种情况下,发现无论点击那个div都输出6,这是因为div节点的onclick是被异步触发的,当事件被触发的时候,for循环早已经结束,此时的变量i已经是6。

解决的办法是,在闭包的帮助下,把每次循环的i都封闭起来,当事件函数顺着作用域链中从内到外查找变量i时,会先找到被封闭在闭包环境中的i,如果有6个div,这里的i就是0,1,2,3,4,5

var nodes = document.getElementsByTagName(‘div‘)
for (var i = 0; i < nodes.length; i++) {
    (function(i){
        nodes[i].onclick = function(){
            console.log(i+1)
        }
    })(i)
}

根据同样的道理,我们还可以编写如下一段代码

var Type = {};

for (var i = 0 , type; type = [‘String‘,‘Array‘,‘Number‘][i++];){
    (function ( type ){
        Type[‘is‘ + type] = function( obj ) {
            return Object.prototype.toString.call( obj ) === ‘[object ‘+ type +‘]‘
        }
    })( type )
}

console.log( Type.isArray([]) ) //true
console.log( Type.isString(‘‘) )//true

3.闭包的更多的作用

在实际开发中,闭包的运用十分广泛

(1)封装变量

闭包可以帮助把一些不需要暴露在全局的变量封装成“私有变量”,假设一个计算乘积的简单函数。

    var mult = function(){
        var a = 1;
        for (var i = 0, l = arguments.length; i < l; i++) {
            a = a * arguments[i]
        }
        return a
    }

    console.log(mult(10,2,4)) //80

mult函数每次都接受一些number类型的参数,并返回这些参数的乘积,现在我们觉得对于那些相同的参数来说,每次都进行一次计算是一种浪费,我们可以加入缓存机制来提高这个函数的性能。

var cache = {};

var mult = function(){
    var args = Array.prototype.join.call( arguments, ‘,‘ );
    if (cache[ args ]) {
        return cache[ args ]
    }

    var a = 1;
    for ( var i = 0, l = arguments.length; i<l;i++ ) {
        a = a * arguments[i]
    }

    return cache[ args ] = a;
}

console.log(mult(10,2,4)) //80

看到cache这个变量仅仅在mult函数中被使用,与其让cache变量跟mult函数一起暴露在全局作用域下,不如将它封装在mult内部,这样可以减少页面的全局变量,以避免在其它地方不小心修改而引发错误。

var mult = (function(){
    var cache = {};
    return function(){
        var args = Array.prototype.join.call( arguments, ‘,‘ );
        if (args in cache){
            return cache[ args ]
        }

        var a = 1;
        for ( var i = 0, l = arguments.length; i < l; i++ ){
            a = a * arguments[i]
        }

        return cache[ args ] = a;
    }
})()

console.log(mult(10,2,4,2)) //160

提炼函数是重构中一种常见的技巧。如果在一个大函数中有一些代码能独立出来,我们常常把这些小代码块封装在独立的小函数里面。独立的小函数有助于代码复用 ,如果这些小函数有一个良好的命名,它们本身起到了注释的作用,这些小函数不需要在程序的其它地方使用,最好是他们用闭包封闭起来。代码如下:

var mult = (function(){
    var cache = {};
    var calculate = function(){//封闭calculate函数
        var a = 1;
        for ( var i = 0, l = arguments.length; i < l; i++ ){
            a = a * arguments[i]
        }
        return a;
    }

    return function(){
        var args = Array.prototype.join.call( arguments, ‘,‘ );
        if ( args in cache ){
            return cache[ args ];
        }
        return cache[ args ] = calculate.apply( null, arguments )
    }
})()
console.log(mult(10,2,4,2,2)) //320

(2)延续局部变量的寿命

img对象经常用于数据的上报,如下所示

var report = function( src ){
    var img = new Image()
    img.src = src;
}
report(‘http://.com/getUserinfo‘)

但是我们结果查询后,得知,因为一些低版本浏览器的实现存在bug,在这些浏览器下使用report函数数据的上报会丢失30%,也就是说,reprot函数并不是每次都发起了请求。
丢失的原因是img是report函数中的局部变量,当report函数的调用结束后,img局部变量随即被销毁,而此时或许还没有来的及发出http请求。所有此次的请求就会丢失掉。

现在我们将img变量用闭包封闭起来,便能解决请求丢失的问题。

var report = (function(){
    var img = [];
    return function( src ){
        var img = new Image();
        img.push( img );
        img.src = src;
    }
})()

4.闭包和面向对象设计

下面我们来看看跟闭包相关的代码:

var extent = function(){
    var value = 0;
    return {
        call : function(){
            value++;
            console.log(value)
        }
    }
};
var bb = extent();

bb.call() //1
bb.call() //2
bb.call() //3

如果换成面向对象的写法,就是:

var extent = {
    value : 0,
    call : function(){
        this.value++;
        console.log(this.value)
    }
}
extent.call();//1
extent.call();//2
extent.call();//3

或者,

var extent = function(){
    this.value = 0;
} 
extent.prototype.call = function(){
    this.value++;
    console.log(this.value)
}

var dd = new extent()
dd.call();//1
dd.call();//2
dd.call();//3

 

此文尚未完结,请关注更新

 

 上一篇文章: (二)this、call和apply

(三)闭包和高阶函数

标签:

原文地址:http://www.cnblogs.com/ahthw/p/5117570.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!