你的位置:首页 > Java教程

[Java教程](三)闭包和高阶函数


虽然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() //3f() //4f() //5f() //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([]) ) //trueconsole.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() //1bb.call() //2bb.call() //3

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

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

或者,

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

 

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

 

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