js中的高阶函数
高阶函数是指至少满足下列条件之一的函数:
1.函数可以作为参数被传递
2.函数可以作为返回值输出
js中的函数显然满足高阶函数的条件,在实际的开发中,无论是将函数当做参数传递 还是让函数的执行结果返回另外一个函数,这两种情形都有很多的应用场景
一:函数作为参数传递
把函数当做参数传递,这代表我们可以抽离出一部分容易变化的业务逻辑把这部分的业务逻辑放在函数参数中,这样一来可以分离业务代码中变化与不变的部分,其中一种重要应用场景就是常见的回调函数。
1.回调函数
在ajax异步请求的应用中,回调函数的使用非常频繁。当我们想在ajax请求返回之后做一些事情,单又不知道请求返回的确切时间,最常见的就是把callback函数当做参数传入发起ajax请求的方法中,请求完成之后执行callback函数;
var getUserInfo = function( userId, callback ){
$.ajax( 'http://xxx.com/getUserInfo?' + userId, function( data ){
if ( typeof callback === 'function' ){
callback( data );
}
});
}
getUserInfo( 13157, function( data ){
alert ( data.userName );
});
回调函数的应用不仅仅只在异步请求中,当一个函数不适合执行一些请求的时候,我们也可以把这些请求封装成一个函数,并把它作为参数传递给另外一个函数,”委托“给另外一个函数来执行。
比如想在页面中创建100个div节点然后将这些div节点设置为隐藏,下面是一种编写代码的方式
var appendDiv = function(){
for(var i = 0;i<100;i++){
var div = document.createElement('div);
div.innerHTML = i;
document.body.appendChild(div);
div.style.display = 'none';
}
}
appendDiv();
上面的代码中 将div.style.display = 'none'的逻辑放在appendDiv里面显然是不合适的,appendDiv有些个性化,成为了一个难以复用的函数,并不是每个人创建了节点之后就希望他们被立刻隐藏。
于是我们把div.style.display = 'none'这行代码抽出来使用回调的函数的形式传入
appendDiv方法:
var appendDiv = function(callback){
for(var i = 0;i<100;i++){
var div = document.createElement('div);
div.innerHTML = i;
document.body.appendChild(div);
if(typeof callback === 'function'){
callback(div);
}
}
}
appendDiv(function(node){
node.style.display = 'none';
})
可以看到隐藏节点的请求实际上是由客户发起的,但是实际上客户并不知道节点什么时候会创建好,于是把隐藏节点的逻辑放在了回调函数中委托给appendDiv方法当然知道节点什么时候创建好所以节点创建好的时候appendDiv会执行之前客户传入的回调函数。
2.Array.prototype.sort
Array.prototype.sort接收一个函数作为参数.这个函数里面封装了数组元素的排序规则从Array.prototype.sort的使用可以看到,我们的目的是对数组进行排序,这是不变的部分。而使用什么规则去排序则是可以变化的部分,把可以变化的部分封装在函数的参数里面动态传入
//从小到大排列
[ 1, 4, 3 ].sort( function( a, b ){
console.log(a,b)
return a - b;
});
// 输出: [ 1, 3, 4 ]
//从大到小排列
[ 1, 4, 3 ].sort( function( a, b ){
return b - a;
});
// 输出: [ 4, 3, 1 ]
二:函数作为返回值输出
相比把函数当做参数传递,函数当做返回值输出的应用场景应该是更多,也更能体现函数式编程技巧让函数继续返回一个可执行的函数,意味着运算过程是可以延续的。
1.判断数据的类型
我们平时判断一个数据是不是一个数组,我们使用的更好的方式是:
Object.prototype.toString 来计算
Object.prototype.toString.call(obj)返回一个字符串
Object.prototype.toString.call([1,2,3])总是返回 "[object Array]"
Object.prototype.toStting.call("str") 总是返回 "[object Strign]"
所以我们可以实现一个 isType 函数
var isString = function(obj){
return Object.prototype.call(obj) === '[object String]';
}
var isArray = function(obj){
return Object.prototype.call(obj) === '[object Array]';
}
var isNumber = function( obj ){
return Object.prototype.toString.call( obj ) === '[object Number]';
};
我们发现,这些函数的大部分的实现都是相同的,不同的只是函数内部返回的字符串。为了避免多余的代码,我们尝试把这些字符串作为参数提前植入isType函数。代码如下:
var isType = function(type){
return function(){
return Object.prototype.toString,call(obj) === '[object'+type+']';
}
}
var isString = isType( 'String' );
var isArray = isType( 'Array' );
var isNumber = isType( 'Number' );
console.log(isArray[1,2,3]);
2.getSingle
下面是一个单例模式模式的例子单例模式作为设计模式的一种重要的组成部分
值得认真的研究一下:
var getSingle = function(fn){
var ret;
return function(){
return ret || (ret = fn.apply(this,arguments));
};
};
这个高阶函数的例子,既把函数当做参数传递,又让函数执行后返回了另外一个函数
我们可以看看getSingle函数的效果:
var getScript = getSingle(function(){
return document.createElement('script');
})
var script1 = getScript();
var script2 = getScript();
alert(script1 === script2); // 输出true
3.高阶函数实现AOP
AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的模块通常包括日志统计、安全控制异常处理等等。
把这些功能抽离出来之后再通过动态嵌入的方式掺入到业务模块之中,这样做的好处就是可以保持业务逻辑模块的纯净和高内聚性,其次可以很方便的复用日志统计等功能模块。
在Java语言中,可以通过反射和动态代理机制来实现AOP技术,而在js这样的动态语言中AOP的实现更加简单这是js与生俱来的能力。
通常在js中实现AOP都是把一个函数动态织入到另外一个函数中,具体的实现技术很多
这里介绍一种通过扩展 Function.prototype 做到这一点:
Function.prototype.before = function(beforefn){
var _seif = this; // 保存原函数的引用。
return function(){ // 返回了包含原函数和新函数的代理函数
beforefn.apply(this.arguments); // 执行新的函数修正this
return _self.apply(this.arguments); // 执行原函数
}
}
Function.prototype.after = function(afterfn){
var _self = this; //
return function(){
var ret = _self.apply(this.arguments);
afterfn.apply(this.arguments);
return ret;
}
}
var func = function(){
console.log(2)
}
func = func.before(function(){
console.log(1)
}).after(function(){
console.log(3)
})
func();
我们把负责打印数字1和打印数字3的两个函数通过AOP的方式动态植入func函数,通过执行上面的代码我们看到执行的效果是:1、2、3
这种使用AOP的方式来给函数添加职责,也是js中一种非常特别和巧妙的
装饰者模式实现,这种模式在开发中非常有用。