- 函数可以作为函数被传入,也称为回调函数,如函数合成运算。
- 可以返回函数作为输出,如函数柯里化运算。
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 单例模式
- JS 实现 AOP
- JS 类型检测
- JS 函数节流和分时函数
- JS 惰性载入函数与分支函数
- JS 偏函数
- JS 泛型函数
由于篇幅有限,本节只介绍前面三种应用场景,其它场景请猛击链接查看。
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
原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/23156.html