标签:
虽然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