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


<button 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中,有语句块,但是却没有相应的块级作用域,但我们可以使用闭包来模仿块级作用域,一般格式为:

复制代码 代码如下:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wddsgs.html