首页 > 编程笔记 > JavaScript笔记 > JS函数

JS高阶函数精讲

高阶函数也称算子(运算符)或泛函。作为函数式编程最显著的特征,高阶函数是对函数运算进行进一步的抽象。高阶函数的形式应至少满足下列条件之一:

JS回调函数

把函数作为值传入另一个参数,当传入参数被调用时,就称为回调函数,即异步调用已绑定的函数。例如,事件处理函数、定时器中的回调函数、异步请求中的回调函数、replace 方法中的替换函数、数组迭代中的回调函数(sort、map、forEach、filter、some、every、reduce 和 reduceRight 等),都是回调函数的不同应用形式。下面仅举两个示例,演示回调函数的应用。

示例1

下面代码根据日期对对象进行排序。
//声明3个对象,每个对象都有属性id和date
var a = {id : 1, date : new Date(2019,3,12)},
    b = {id : 2, date : new Date(2019,1,14)},
    c = {id : 3, date : new Date(2019,2,26)};
var arr = [a,b,c];
arr.sort(function(x,y){
    return x.date-y.date;
});
for (var i = 0; i < arr.length; i++) {
console.log(arr[i].id + " " + arr[i].date.toLocaleString());
}
输出结果:

2 2019 年 2 月 14 日 0:00:00
3 2019 年 3 月 26 日 0:00:00
1 2019 年 4 月 12 日 0:00:00

在数组排序的时候,会迭代数组每个元素,并逐一调用回调函数 function(x,y) {return x.date - y.date}。

示例2

在《JS map()》一节中我们曾介绍过数组的 map 方法,实际上很多函数式编程语言均有此函数。其语法格式为:

map(array,func)

map 表达式将 func 函数作用于 array 的每一个元素,并返回一个新的 array。

下面使用 JavaScript 实现 map(array,func) 表达式运算。
function map(array,func) {
    var res = [];
    for (var i in array) {
        res.push(func(array[i]));
    }
    return res;
}
console.log(map([1,3,5,7,8], function (n) {  //返回元素值的平方
    return n * n;
}));  //1,9,25,49,64
console.log(map(["one", "two", "three", "four"], function(item) {  //返回首字母大写
    return item[0].toUpperCase() + item.slice(1).toLowerCase();
}));  //One,Two,Three,Four
两次调用 map,却得到了截然不同的结果,是因为 map 的参数本身已经进行了一次抽象,map 函数做的是第二次抽象。注意:高阶的“阶”可以理解为抽象的层次。

JS 函数既可以作为参数传入函数内部,也可以作为返回值 return 到函数外部,具体应用场景包括:
由于篇幅有限,本节只介绍前面三种应用场景,其它场景请猛击链接查看。

JS单例模式

单例就是保证一个类只有一个实例。实现方法:先判断实例是否存在,如果存在则直接返回,否则就创建实例再返回。

单例模式可以确保一个类型只有一个实例对象。在 JavaScript 中,单例可以作为一个命名空间,提供一个唯一的访问点来访问该对象。单例模式封装代码如下:
var getSingle = function (fn) {
    var ret;
    return function () {
        return ret || (ret = fn.apply(this, arguments));
    };
};

示例1

在脚本中定义 XMLHttpRequest 对象。由于一个页面可能需要多次创建异步请求对象,使用单例模式封装之后,就不用重复创建实例对象,共用一个即可。
function XHR () {  //定义XMLHttpRequest 对象
    return new XMLHttpRequest();
}
var xhr = getSingle(XHR);  //封装XHR实例
var a = xhr();  //实例1
var b = xhr();  //实例2
console.log(a === b);  //true,说明这两个实例实际上相同

示例2

可以限定函数仅能调用一次,避免重复调用,这在事件处理函数中非常有用。
<button>仅能点击一次</button>
<script>
function getSingle (fn) {
    var ret;
    return function () {
        return ret || (ret = fn.apply(this,arguments));
    };
};
var f = function () { console.log(this.nodeName); }  //事件处理函数
document.getElementsByTagName("button")[0].onclick = getSingle(f);
</script>

JS实现 AOP

AOP(面向切面编程)就是把一些与业务逻辑模块无关的功能抽离出来,如日志统计、安全控制、异常处理等,然后通过“动态织入”的方式掺入业务逻辑模块中。这样设计的好处是:首先可以保证业务逻辑模块的纯净和高内聚性;其次可以方便地复用日志统计等功能模块。

示例

在 JavaScript 中实现 AOP,一般是把一个函数“动态织入”到另外一个函数中。具体的实现方法有很多,下面通过扩展 Function.prototype 方法实现 AOP。
Function.prototype.before = function (beforefn) {
    var __self = this;  //保存原函数的引用
    return function () {  //返回包含了原函数和新函数的“代理”函数
        beforefn.apply(this, arguments);  //执行新函数
        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);  //执行新函数,修正this
        return ret;
    }
};
var func = function (){
    console.log(2);
};
func = func.before(function () {
    console.log(1);
}).after(function () {
    console.log(3)
});
func();  //按顺序输出1,2,3

类型检测

本节利用 JavaScript 高阶函数特性来重新设计 typeOf() 函数,并提供单项类型判断函数。

【实现代码】

function typeOf(obj) {  //类型检测函数,返回字符串表示
    var str = Object.prototype.toString.call(obj);
    return str.match(/\[object(.*?)\]/)[1].toLowerCase();
};
['null', 'Undefined', 'Object', 'Array', 'String', 'Number', 'Boolean', 'Function', 'RegExp'].forEach(function (t) {  //类型判断,返回布尔值
    typeOf['is' + t] = function (o) {
        return typeOf(o) === t.toLowerCase();
    };
});

【应用代码】

//类型检测
console.log(typeOf({}));  //"object"
console.log(typeOf([]));  //"array"
console.log(typeOf(0));  //"number"
console.log(typeOf(null));  //"null"
console.log(typeOf(undefined));  //"undefined"
console.log(typeOf(//));  //"regex"
console.log(typeOf(new Date()));  //"date"
//类型判断
console.log(typeOf.isObject({}));  //true
console.log(typeOf.isNumber(NaN));  //true
console.log(typeOf.isRegExp(true));  //false

所有教程

优秀文章