JavaScript高级程序设计(第3版)学习笔记9 js函数(下)


Posted in Javascript onOctober 11, 2012

再接着看函数——具有魔幻色彩的对象。

9、作为值的函数

在一般的编程语言中,如果要将函数作为值来使用,需要使用类似函数指针或者代理的方式来实现,但是在ECMAScript中,函数是一种对象,拥有一般对象具有的所有特征,除了函数可以有自己的属性和方法外,还可以做为一个引用类型的值去使用,实际上我们前面的例子中已经有过将函数作为一个对象属性的值,又比如函数也可以作为另一个函数的参数或者返回值,异步处理中的回调函数就是一个典型的用法。

var name = 'linjisong'; 
var person = {name:'oulinhai'}; 
function getName(){ 
return this.name; 
} 
function sum(){ 
var total = 0, 
l = arguments.length; 
for(; l; l--) 
{ 
total += arguments[l-1]; 
} 
return total; 
} // 定义调用函数的函数,使用函数作为形式参数 
function callFn(fn,arguments,scope){ 
arguments = arguments || []; 
scope = scope || window; 
return fn.apply(scope, arguments); 
} 
// 调用callFn,使用函数作为实际参数 
console.info(callFn(getName));//linjisong 
console.info(callFn(getName,'',person));//oulinhai 
console.info(callFn(sum,[1,2,3,4]));//10

再看一个使用函数作为返回值的典型例子,这个例子出自于原书第5章:
function createComparisonFunction(propertyName) { 
return function(object1, object2){ 
var value1 = object1[propertyName]; 
var value2 = object2[propertyName]; if (value1 < value2){ 
return -1; 
} else if (value1 > value2){ 
return 1; 
} else { 
return 0; 
} 
}; 
} 
var data = [{name: "Zachary", age: 28}, {name: "Nicholas", age: 29}]; 
data.sort(createComparisonFunction("name")); 
console.info(data[0].name); //Nicholas 
data.sort(createComparisonFunction("age")); 
console.info(data[0].name); //Zachary

10、闭包(Closure)

闭包是指有权访问另一个函数作用域中的变量的函数。对象是带函数的数据,而闭包是带数据的函数。

首先闭包是一个函数,然后闭包是一个带有数据的函数,那么,带有的是什么数据呢?我们往上看看函数作为返回值的例子,返回的是一个匿名函数,而随着这个匿名函数被返回,外层的createComparisonFunction()函数代码也就执行完成,按照前面的结论,外层函数的执行环境会被弹出栈并销毁,但是接下来的排序中可以看到在返回的匿名函数中依旧可以访问处于createComparisonFunction()作用域中的propertyName,这说明尽管createComparisonFunction()对应的执行环境已经被销毁,但是这个执行环境相对应的活动对象并没有被销毁,而是作为返回的匿名函数的作用域链中的一个对象了,换句话说,返回的匿名函数构成的闭包带有的数据就是:外层函数相应的活动对象。由于活动对象的属性(也就是外层函数中定义的变量、函数和形式参数)会随着外层函数的代码执行而变化,因此最终返回的匿名函数构成的闭包带有的数据是外层函数代码执行完成之后的活动对象,也就是最终状态。

希望好好理解一下上面这段话,反复理解一下。虽然我已经尽我所能描述的更易于理解一些,但是闭包的概念还是有些抽象,下面看一个例子,这个例子来自原书第7章:

function createFunctions(){ 
var result = new Array(); 
for (var i=0; i < 10; i++){ 
result[i] = function(){ 
return i; 
}; 
} 
return result; 
} var funcs = createFunctions(); 
for (var i=0,l=funcs.length; i < l; i++){ 
console.info(funcs[i]());//每一个函数都输出10 
}

这里由于闭包带有的数据是createFunctions相应的活动对象的最终状态,而在createFunctions()代码执行完成之后,活动对象的属性i已经变成10,因此在下面的调用中每一个返回的函数都输出10了,要处理这种问题,可以采用匿名函数作用域来保存状态:
function createFunctions(){ 
var result = new Array(); 
for (var i=0; i < 10; i++){ 
result[i] = (function(num){ 
return function(){ 
return num; 
}; 
})(i); 
} 
return result; 
}

将每一个状态都使用一个立即调用的匿名函数来保存(保存在匿名函数相应的活动对象中),然后在最终返回的函数被调用时,就可以通过闭包带有的数据(相应的匿名函数活动对象中的数据)来正确访问了,输出结果变成0,1,...9。当然,这样做,就创建了10个闭包,在性能上会有较大影响,因此建议不要滥用闭包,另外,由于闭包会保存其它执行环境的活动对象作为自身作用域链中的一环,这也可能会造成内存泄露。尽管闭包存在效率和内存的隐患,但是闭包的功能是在太强大,下面就来看看闭包的应用——首先让我们回到昨天所说的函数绑定方法bind()。

(1)函数绑定与柯里化(currying)

A、再看this,先看一个例子(原书第22章):

<button id='my-btn' title='Button'>Hello</button> 
<script type="text/javascript"> 
var handler = { 
title:'Event', 
handleClick:function(event){ 
console.info(this.title); 
} 
}; 
var btn = document.getElementById('my-btn');//获取页面按钮 
btn.onclick = handler.handleClick;//给页面按钮添加事件处理函数 
</script>

如果你去点击“Hello”按钮,控制台打印的是什么呢?竟然是Button,而不是期望中的Event,原因就是这里在点击按钮的时候,处理函数内部属性this指向了按钮对象。可以使用闭包来解决这个问题:
btn.onclick = function(event){ 
handler.handleClick(event);//形成一个闭包,调用函数的就是对象handler了,函数内部属性this指向handler对象,因此会输出Event}

B、上面的解决方案并不优雅,在ES5中新增了函数绑定方法bind(),我们使用这个方法来改写一下:
if(!Function.prototype.bind){//bind为ES5中新增,为了保证运行正常,在不支持的浏览器上添加这个方法 
Function.prototype.bind = function(scope){ 
var that = this;//调用bind()方法的函数对象 
return function(){ 
that.apply(scope, arguments);//使用apply方法,指定that函数对象的内部属性this 
}; 
}; 
} 
btn.onclick = handler.handleClick.bind(handler);//使用bind()方法时只需要使用一条语句即可

这里添加的bind()方法中,主要技术也是创建一个闭包,保存绑定时的参数作为函数实际调用时的内部属性this。如果你不确定是浏览器本身就支持bind()还是我们这里的bind()起了作用,你可以把特性检测的条件判断去掉,然后换个方法名称试试。
C、上面对函数使用bind()方法时,只使用了第一个参数,如果调用bind()时传入多个参数并且将第2个参数开始作为函数实际调用时的参数,那我们就可以给函数绑定默认参数了。
if(!Function.prototype.bind){ 
Function.prototype.bind = function(scope){ 
var that = this;//调用bind()方法的函数对象 
var args = Array.prototype.slice.call(arguments,1);//从第2个参数开始组成的参数数组 
return function(){ 
var innerArgs = Array.prototype.slice.apply(arguments); 
that.apply(scope, args.concat(innerArgs));//使用apply方法,指定that函数对象的内部属性this,并且填充绑定时传入的参数 
}; 
}; 
}

D、柯里化:在上面绑定时,第一个参数都是用来设置函数调用时的内部属性this,如果把所有绑定时的参数都作为预填的参数,则称之为函数柯里化。
if(!Function.prototype.curry){ 
Function.prototype.curry = function(){ 
var that = this;//调用curry()方法的函数对象 
var args = Array.prototype.slice.call(arguments);//预填参数数组 
return function(){ 
var innerArgs = Array.prototype.slice.apply(arguments);//实际调用时参数数组 
that.apply(this, args.concat(innerArgs));//使用apply方法,并且加入预填的参数 
}; 
}; 
}

(2)利用闭包缓存

还记得前面使用递归实现斐波那契数列的函数吗?使用闭包缓存来改写一下:

var fibonacci = (function(){//使用闭包缓存,递归 
var cache = []; 
function f(n){ 
if(1 == n || 2 == n){ 
return 1; 
}else{ 
cache[n] = cache[n] || (f(n-1) + f(n-2)); 
return cache[n]; 
} 
} 
return f; 
})(); var f2 = function(n){//不使用闭包缓存,直接递归 
if(1 == n || 2 == n){ 
return 1; 
}else{ 
return f2(n-1) + f2(n-2); 
} 
};

下面是测试代码以及我机器上的运行结果:
var test = function(n){ 
var start = new Date().getTime(); 
console.info(fibonacci(n)); 
console.info(new Date().getTime() - start); start = new Date().getTime(); 
console.info(f2(n)); 
console.info(new Date().getTime() - start); 
}; 
test(10);//55,2,55,2 
test(20);//6765,1,6765,7 
test(30);//832040,2,832040,643

可以看到,n值越大,使用缓存计算的优势越明显。作为练习,你可以尝试自己修改一下计算阶乘的函数。

(3)模仿块级作用域

在ECMAScript中,有语句块,但是却没有相应的块级作用域,但我们可以使用闭包来模仿块级作用域,一般格式为:

(function(){ 
//这里是块语句 
})();

上面这种模式也称为立即调用的函数表达式,这种模式已经非常流行了,特别是由于jQuery源码使用这种方式而大规模普及起来。

闭包还有很多有趣的应用,比如模仿私有变量和私有函数、模块模式等,这里先不讨论了,在深入理解对象之后再看这些内容。

关于函数,就先说这些,在网上也有很多非常棒的文章,有兴趣的可以自己搜索一下阅读。这里推荐一篇文章,《JavaScript高级程序设计(第3版)》译者的一篇译文:命名函数表达式探秘。

Javascript 相关文章推荐
jsvascript图像处理—(计算机视觉应用)图像金字塔
Jan 15 Javascript
在Javascript中 声明时用&quot;var&quot;与不用&quot;var&quot;的区别
Apr 15 Javascript
JavaScript的Module模式编程深入分析
Aug 13 Javascript
javascript运行机制之this详细介绍
Feb 07 Javascript
javascript相关事件的几个概念
May 21 Javascript
JavaScript检测上传文件大小的方法
Jul 22 Javascript
jquery插件jquery.confirm弹出确认消息
Dec 22 Javascript
基于JS实现密码框(password)中显示文字提示功能代码
May 27 Javascript
javascript实现获取指定精度的上传文件的大小简单实例
Oct 25 Javascript
jquery做个日期选择适用于手机端示例
Jan 10 Javascript
详解Javascript中new()到底做了些什么?
Mar 29 Javascript
H5+C3+JS实现双人对战五子棋游戏(UI篇)
May 28 Javascript
JavaScript高级程序设计(第3版)学习笔记8 js函数(中)
Oct 11 #Javascript
JavaScript高级程序设计(第3版)学习笔记7 js函数(上)
Oct 11 #Javascript
JavaScript高级程序设计(第3版)学习笔记6 初识js对象
Oct 11 #Javascript
JavaScript高级程序设计(第3版)学习笔记5 js语句
Oct 11 #Javascript
JavaScript高级程序设计(第3版)学习笔记4 js运算符和操作符
Oct 11 #Javascript
JavaScript高级程序设计(第3版)学习笔记3 js简单数据类型
Oct 11 #Javascript
JavaScript高级程序设计(第3版)学习笔记2 js基础语法
Oct 11 #Javascript
You might like
《星际争霸》各版本雷兽特点图文解析 雷兽不同形态一览
2020/03/02 星际争霸
php图片处理:加水印、缩略图的实现(自定义函数:watermark、thumbnail)
2010/12/02 PHP
php 操作数组(合并,拆分,追加,查找,删除等)
2012/07/20 PHP
php异步:在php中使用fsockopen curl实现类似异步处理的功能方法
2016/12/10 PHP
PHP实现八皇后算法
2019/05/06 PHP
laravel按天、按小时,查询数据的实例
2019/10/09 PHP
javascript与asp.net(c#)互相调用方法
2009/12/13 Javascript
js修改input的type属性及浏览器兼容问题探讨与解决
2013/01/23 Javascript
使用jQuery和PHP实现类似360功能开关效果
2014/02/12 Javascript
jQuery照片伸缩效果不影响其他元素的布局
2014/05/09 Javascript
javascript使用avalon绑定实现checkbox全选
2015/05/06 Javascript
COM组件中调用JavaScript函数详解及实例
2017/02/23 Javascript
React-Native之定时器Timer的实现代码
2017/10/04 Javascript
浅谈Node.js 沙箱环境
2018/05/15 Javascript
一分钟学会JavaScript中的try-catch
2020/12/14 Javascript
详解Vue的七种传值方式
2021/02/08 Vue.js
跟老齐学Python之数据类型总结
2014/09/24 Python
Python 中urls.py:URL dispatcher(路由配置文件)详解
2017/03/24 Python
Python+Selenium+PIL+Tesseract自动识别验证码进行一键登录
2017/09/20 Python
简单了解Python3 bytes和str类型的区别和联系
2019/12/19 Python
Python列表去重复项的N种方法(实例代码)
2020/05/12 Python
Python字符串函数strip()原理及用法详解
2020/07/23 Python
jupyter 添加不同内核的操作
2021/02/06 Python
东南亚地区最大的购物网站Lazada新加坡站点:Lazada.sg
2016/07/17 全球购物
Banana Republic欧盟:美国都市简约风格的代表品牌
2018/05/09 全球购物
美国婚礼装饰和活动用品批发供应商:Event Decor Direct
2018/10/12 全球购物
Yves Rocher伊夫·黎雪美国官网:法国始创植物美肌1959
2019/01/09 全球购物
应届大学生的推荐信
2013/11/20 职场文书
一个大学生十年的职业规划
2014/01/17 职场文书
工程索赔意向书
2014/08/30 职场文书
殡葬服务心得体会
2014/09/11 职场文书
结婚当天新郎保证书
2015/05/08 职场文书
pandas数值排序的实现实例
2021/07/25 Python
手把手教你导入Go语言第三方库
2021/08/04 Golang
Go 中的空白标识符下划线
2022/03/25 Golang
Vue深入理解插槽slot的使用
2022/08/05 Vue.js