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 相关文章推荐
CSS常用网站布局实例
Apr 03 Javascript
Firefox div高度自适应
Apr 28 Javascript
Javascript学习笔记8 用JSON做原型
Jan 11 Javascript
jQuery ctrl+Enter shift+Enter实现代码
Feb 07 Javascript
js去空格技巧分别去字符串前后、左右空格
Oct 21 Javascript
node.js中的events.emitter.listeners方法使用说明
Dec 10 Javascript
JavaScript实现cookie的写入、读取、删除功能
Nov 05 Javascript
深入浅析AngularJS中的module(模块)
Jan 04 Javascript
React Native实现简单的登录功能(推荐)
Sep 19 Javascript
JavaScript定时器setTimeout()和setInterval()详解
Aug 18 Javascript
在vue项目中引入高德地图及其UI组件的方法
Sep 04 Javascript
html中创建并调用vue组件的几种方法汇总
Nov 17 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
php中try catch捕获异常实例详解
2014/11/21 PHP
Jsonp 跨域的原理以及Jquery的解决方案
2010/05/18 Javascript
JavaScript 类型的包装对象(Typed Wrappers)
2011/10/27 Javascript
css3元素简单的闪烁效果实现(html5 jquery)
2013/12/28 Javascript
jquery无刷新验证邮箱地址实现实例
2014/02/19 Javascript
DataTables+BootStrap组合使用Ajax来获取数据并且动态加载dom的方法(排序,过滤,分页等)
2016/11/09 Javascript
jQuery实现导航回弹效果
2017/02/27 Javascript
highcharts 在angular中的使用示例代码
2017/09/20 Javascript
使用Nuxt.js改造已有项目的方法
2018/08/07 Javascript
在vue项目中正确使用iconfont的方法
2018/09/28 Javascript
JS实现的碰撞检测与周期移动完整示例
2019/09/02 Javascript
微信小程序实现蓝牙打印
2019/09/23 Javascript
使用Python中的线程进行网络编程的入门教程
2015/04/15 Python
python实现对文件中图片生成带标签的txt文件方法
2018/04/27 Python
解决每次打开pycharm直接进入项目的问题
2018/10/28 Python
浅析python3字符串格式化format()函数的简单用法
2018/12/07 Python
python实现画五角星和螺旋线的示例
2019/01/20 Python
Django框架验证码用法实例分析
2019/05/10 Python
Python hashlib加密模块常用方法解析
2019/12/18 Python
解决Keras TensorFlow 混编中 trainable=False设置无效问题
2020/06/28 Python
python实现梯度下降算法的实例详解
2020/08/17 Python
Python 实现图片转字符画的示例(静态图片,gif皆可)
2020/11/05 Python
纽约家具、家居装饰和地毯店:ABC Carpet & Home
2017/06/21 全球购物
系统管理员的职责包括那些?管理的对象是什么?
2016/09/20 面试题
介绍一下Linux内核的排队自旋锁
2014/08/27 面试题
交通专业个人自荐信格式
2013/09/23 职场文书
夜大毕业生自我鉴定
2013/10/31 职场文书
就业自荐信
2013/12/04 职场文书
小学生新学期寄语
2014/01/19 职场文书
元旦联欢会策划方案
2014/06/11 职场文书
家长学校教学计划
2015/01/19 职场文书
2015年幼儿园中班工作总结
2015/04/25 职场文书
借款民事起诉状范文
2015/05/19 职场文书
初婚初育证明范本
2015/06/18 职场文书
企业安全生产规章制度
2015/08/06 职场文书
读鲁迅先生的经典名言
2019/08/20 职场文书