1. 高阶函数的特点
- 函数作为参数(回调)
function fn(callback) {
callback()
}
- 函数作为返回值
function fn() {
return function() {
}
}
只要符合任一特点,此函数就可以称作
高阶函数或者高级函数
2. 高级函数的使用场景
1.你可能使用这种方式判断过数据类型
// Array => [object Object]
// Object => [object Object]
// Function => [object Function]
// RegExp => [object RegExp]
// Date => [object Date]
// String => [object String]
// Number => [object Number]
// undefined => [object Undefined]
// null => [object null]
// boolean => [object Boolean]
// Symbol => [object Symbol]
function isArray(value) { // {1}
return Object.prototype.toString.call(value) === '[object Array]';
}
// 或者
function checkType(value, type) { // {2}
return Object.prototype.toString.call(value) === `[object ${type}]`;
}
const isString = checkType('abc', 'String');
使用上面的方式,每次检查类型,都要传递两个参数,而且第二个“类型”参数确实很多余,下面我们使用高级函数对检测类型函数进行优化。
function checkType(type) { // {3}
return function(value) {
return Object.prototype.toString.call(value) === `[object ${type}]`;
}
}
const isString = checkType('String');
const isArray = checkType('Array');
isString('abc'); // true;
第{3}种方式,相较于{1}{2}种方式,在代码的复用性和适用性方面明显提高了很多。就是中间嵌套了一层有点难受。我将在函数柯里化章节说明进一步优化方案,主要解决函数嵌套问题。
2.AOP(Aspect-Oriented Programming)面向切片编程
主要是把跟核心无关的代码的抽离出来,其实就是在原函数外层嵌套一层,不用管原函数的实现。
比如,我们要在原函数之前或者之后执行一些逻辑。
function say() {
console.log('Hello World!');
}
// 我们想要在执行say之前处理一些事情
Function.prototype.before = function(callback) {
return ()=> {
callback();
this();
}
}
const beforeFn = say.before(function() {
console.log('say');
})
beforeFn(); // 1.say 2.hello world!;
3.异步代码计数器
在日常开发中,有时候我们需要拿到多个异步代码的结果进行统一归纳,但这些异步代码彼此之前又不相关联,所以为了避免代码难以阅读,我们不能去写一些异步嵌套代码,这个时候,我们可以使用计数器的方式来处理这种场景。
const fs = require('fs');
function after(times, callback) {
const result = {};
return function (key, data) {
result[key] = data;
if (--times === 0) {
callback(result);
}
};
}
// after中的回调会在异步执行之后执行
const newFn = after(2, function (result) {
console.log(result);
});
fs.readFile('./assets/name.txt', 'utf8', function (err, data) {
if (err) return console.log(err);
newFn('name', data);
});
fs.readFile('./assets/age.txt', 'utf8', function (err, data) {
if (err) return console.log(err);
newFn('age', data);
});
我们也不知道name.txt和age.text文件哪个先读完,所以做个计数器,只有当计数器times为零的时候,才会执行回调函数。将最终结果传入回调函数中。
当然,在ES6以后,完全可以用Promise.all去解决上述问题。其实Promise也是基于回调实现的。我会在后续文章中基于Promise A+规范实现Promise。
3. 函数柯里化
柯里化(英语:Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数
举个例子:
// js高级程序设计中的例子
function add(num1, num2) {
return num1 + num2;
}
function curriedAdd(num2) {
return add(5, num2);
}
console.log(2,3); // 5;
console.log(curriedAdd(3)); //8
好吧,我骗了你,上面的例子curriedAdd并不是柯里化函数,他只是设定了add函数的一个实参。但是我们柯里化函数就是上面例子中展示的减少传参的那种效果。
比如我要柯里化上面提到过的checkType函数:
function checkType(type, value) { // {2}
return Object.prototype.toString.call(value) === `[object ${type}]`;
}
function currying(fn, arr=[]) {
const len = fn.length; // 获取原函数参数的个数
return (...args)=> {
const params = [...arr, ...args]; //组合参数
if (params.length < len) {
// 传递的参数已经达到原函数参数的个数,就可以执行函数了
// 否则,递归
return currying(fn, params)
}
return fn(...params);
}
}
const isString = currying(checkType)('String')(1);
// 还可以这样使用
const isArray = currying(checkType)('Array');
isArray([]);
4. 惰性函数
因为浏览器之间行为存在差异,多数JavaScript代码包含了大量的if语句,将执行引导到正确的代码中。比如判断事件监听。
function addEvent (type, el, fn) {
if (window.addEventListener) {
addEvent = function (type, el, fn) {
el.addEventListener(type, fn, false);
}
}
else if(window.attachEvent){
addEvent = function (type, el, fn) {
el.attachEvent('on' + type, fn);
}
}
}
每次调用addEvent(),他都要对浏览器所支持的能力仔细检查。如果浏览器支持addEventListener,那么他就一直支持了,那么这种测试就变得没必要了。即使只有一个if语句每页肯定要比没有if语句的慢,所以如果if语句不必每次执行,那么代码可以运行的更快一些。解决方案就是称之为惰性载入的技巧。
惰性载入表示函数执行的分支仅会发生一次。有两种实现惰性载入的方式:
- 在函数被调用时再处理函数。在第一次调用的过程中,该函数会被覆盖为另外一个按合适方式执行的函数,这样任何对原函数的调用都不用经过执行的分支了。
function addEvent (type, el, fn) {
if (window.addEventListener) {
addEvent = function (type, el, fn) {
el.addEventListener(type, fn, false);
}
}
else if(window.attachEvent){
addEvent = function (type, el, fn) {
el.attachEvent('on' + type, fn);
}
}
}
- 第二种实现惰性载入的方式是在声明函数时就指定适当的函数。这样,第一次调用函数时就不会损失性能了,而在代码首次加载时会损失一点性能。
const addEvent = (function(){
if (window.addEventListener) {
return function (type, el, fn) {
el.addEventListener(type, fn, false);
}
}
else if(window.attachEvent){
return function (type, el, fn) {
el.attachEvent('on' + type, fn);
}
}
})();
5.参考文章
- JavaScript高级程序设计(第三版)
- JavaScript专题之惰性函数