高阶函数 + 柯里化【JS深入知识汇点4】

524 阅读4分钟

系列文章:

什么式函数式编程

函数是一种描述集合和集合之间的转换关系,输入通过函数都会返回有且只有一个输出值。

函数式编程(Functional Programming)就是强调在编程过程中,把更多的关注点放在如何去构建关系。函数式编程大多时候都是在声明我需要什么,而非怎么做。

函数式编程的特点:

  • 函数是“一等公民”:这是函数式编程得以实现的前提,因为我们基本都在操作函数。函数和其他变量一样,可以作为其他函数的输入和输出。
  • 惰性执行:函数只在需要的时候执行,即不产生无意义的中间变量
  • 无状态:主要是强调对于一个函数,不管何时运行,它都像第一次运行一样,给定相同的输入,给出相同的输出,完全不依赖外部状态的变化
  • 数据不可变:所有的数据都是不可变的,意味着如果想改一个对象,就应该先创建一个新的对象用来修改。
  • 纯函数:就是没有副作用的函数

函数式编程的优劣势

优势:

  • 更好的状态管理,能最大化的减少未知,优化代码,减少出错的情况
  • 更简单的复用:把过程逻辑以纯函数来实现,这样代码复用起来,完全不考虑它的内部实现和外部影响
  • 更优雅的组合

劣势:

  • 提高了代码编写成本
  • 跟过程式编程相比,它没有提高性能,反而可能会降低性能
  • 代码不易读
  • 学习成本高

高阶函数

What‘s Higher-order function(高阶函数)

至少满足下列一个条件的函数:

  • 接受一个或多个函数作为输入
  • 输出一个函数

tip:为什么js中,函数是一等公民呢?这是因为:js中,函数是一种特殊的对象,可以将他们作为参数传递给另外的函数,所以称为一等公民

内置高阶函数 => 不使用高阶函数的方案

Array.prototype.map

const arr = [1, 3, 5]
const arr1 = arr.map(item => item * 2)
//不使用高阶函数
let arr2 = [];
for(let i = 0; i < arr.length; i++) {
    arr2.push(arr[i] * 2)
}

Array.prototype.filter

const arr = [1, 3, 5]
const arr1 = arr.filter(item => item > 2)
//不使用高阶函数
let arr2 = [];
for(let i = 0; i < arr.length; i++) {
    (arr[i] > 2) && arr2.push(arr[i])
}

柯里化

What‘s Currying(柯里化)

柯里化,又称部分求值,只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。把接收多个的参数的函数变换成接受一个单一参数的函数,并返回接受余下的参数并且返回结果的新函数。

柯里化作用

  1. 参数复用:缓存参数到闭包内部参数,然后在函数内部将缓存
  2. 提前返回
  3. 延迟计算/运行:不断的柯里化,累积传入的参数,最后执行。

偏函数

偏函数就是固定函数的某一个或几个参数,返回一个新函数,接收剩下的参数,

function partial(fn) {
	var args = [].slice.call(arguments, 1)
    return function() {
    	var newArgs = args.concat([].slice.call(arguments))
        return fn.apply(this, newArgs)
    }
}

难题解析:

Q1:写一个函数实现以下功能:

add(1); // 1
add(1)(2);  // 3
add(1)(2)(3); // 6
add(1)(2)(3)(4); // 10 
function add(x) {
    var sum = x;
    function temp(y) {
        sum = sum + y;
        return temp;
    }
    temp.toString = function() {
        return sum
    }
    return temp
}

Q2:写一个函数实现以下功能:

add(1)(); // 1
add(1)(2)();  // 3
add(1)(2)(3)(); // 6
add(1)(2)(3)(4)(); // 10 
function add(x) {
    var sum = x;
    return function temp(y) {
        if(arguments.length === 0) {
            return sum
        } else {
            sum += y;
            return temp
        }
    }
}

Q3:写一个函数实现以下功能:

add(1, 2, 3) // 6
add(1, 2)(3) // 6
add(1)(2)(3) // 6
add(1)(2, 3) // 6
//思路就是:判断当前参数长度够不够,
function currying(fn, length) {
    // 第一次等于 fn 的参数长度,后续等于 fn 剩余参数的长度
    length = length || fn.length;
    console.log(length)
    return function(...args) {
        // 新函数接受长度如果大于剩余参数的长度,执行 fn 函数,传入 新函数的参数
        // 否则,递归 currying 函数,新的 fn 为绑定了新函数参数的新函数,length为fn剩余的参数
        return args.length >= length ? fn.apply(this, args) : currying(fn.bind(this, ...args), length - args.length)
    }
}
let add = currying(function(a, b, c) {
    return a + b + c
});

Q4: 编写一个程序将数组扁平化去并除其中重复部分数据,最终得到一个升序且不重复的数组

var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 10, [14] ] ] ], 10];
// 方法1
function flatArr(data) {
	let result = [];
	function iterate(arr) {
		if (Array.isArray(arr)) {
			arr.forEach(item => iterate(item))
		} else {
			(!result.includes(arr)) && result.push(arr)
		}
	}
	iterate(data);
    return result.sort((x, y) => x-y)
}
// 方法 2:
data.flat(Infinity).sort((x, y) => x - y).reduce((acc, cur) => {
	if (!acc.includes(cur)) {
		 acc.push(cur)
	}
	return acc
},[])
// 方法 3:
Array.from(new Set(data.flat(Infinity))).sort((x, y) => x - y)

console.log(flatArr(arr))