JS 高阶函数

更新日期: 2019-12-22阅读: 1.9k标签: 函数

Function Object

什么是函数?在大多数编程语言中,函数是一段独立的代码块,用来抽象处理某些通用功能的方法;主要操作是给函数传入特定对象(参数),并在方法调用结束后获得一个新的对象(返回值)。

function greeting(name) {
  return `Hello ${name}`;
}

console.log( greeting('Onion') ); // Hello Onion

但是在 Javascript、Haskell、Clojure 这类语言中,函数是另一种更高级的存在,俗称一等公民;它除了是代码块以外,它还是一种特殊类型的对象——Function Object。

为什么说 Fuction 也是对象呢?还是看上面的示例函数——greeting,我们事实上是可以打印出它的固有属性(properties)的:

console.log(greeting.length, greeting.name);  // 1 'greeting'

这里length是参数列表长度,name就是它定义的名字了。是不是和对象很接近了?我们甚至可以给它添加新的属性和方法:

greeting.displayName = 'Garlic';
greeting.innerName = () => 'Ginger';

console.log(greeting.displayName); // Garlic
console.log(greeting.innerName()); // Ginger

是吧?这么看,函数已经包含了几乎所有的 Object 功能了。当然,生产中尽量不要给函数添加随机属性,毕竟代码是给人阅读的,不要随便增加团队的认知成本。


high order function

上面提到了函数是一种特殊的对象,因此在 js 语言中,函数事实上也可以像普通 object 一样成为其他函数里的参数或是返回值。我们将参数或是范围值为函数的函数称为高阶函数

Higher-Order function is a function that receives a function as an argument or returns the function as output


Function 参数

我们先看一下函数如何成为参数,最经典的案例就是 Array#map。给了例子,实现一个让数组所有元素+1 的操作,传统的做法如下所示:

const arr1 = [1, 2, 3];
const arr2 = [];

for(let i = 0; i < arr1.length; i++) {
  arr2.push(++arr1[i]);
}
console.log(arr2)

如果使用高阶函数 map:

const arr1 = [1, 2, 3];

const arr3 = arr1.map( function callback(element, index, array) {
  return element+1;
});

console.log(arr3); // [2, 3, 4]

map 是 Array.prototype 的原生方法,它的第一个参数是一个 callback 函数,第二个参数是用来绑定 callback 的 this。这里,callback 的作用是迭代调用数组里的元素,并将返回值组装成一个新的数组。这个 map 的函数参数本身还有三个参数:element,index 和 array,分别表示迭代时的元素,索引,以及原始数组。

上面的代码使用 es6 的箭头函数,可以写得更简洁一点:

const arr1 = [1, 2, 3];

const arr3 = arr1.map(e => e+1);

console.log(arr3); // [2, 3, 4]

讲真,我们经常用到高阶函数,Array 里还有好多类似的函数,如 fliter、reduce 等等。这类高阶函数可以明显的改善代码质量,并切能确保不会对原始数组产生副作用。


Fucntion 返回值

返回值是函数的函数,我们也经常使用,最著名的就是 Function#bind。

给个案例,如下函数 greeting 会打印出this的name,但是 greeting 并不是一个纯函数,因为它的 this 绑定不明确,可能会在不同的运行上下文中会返回不同的结果。

function greeting() {
  return `Hello ${this.name}`;
}

如果想明确它的结果该怎么办呢?嗯,为 greeting 绑定一个 object。这个 helloOnion 就是greeting.bind后返回的新函数。

let helloOnoin = greeting.bind({name: 'Onion'});

console.log(helloOnoin()); // Hello Onion

bind方法创建一个新的函数,在bind被调用时,这个新函数的this被bind的第一个参数指定,其余的参数将作为新函数的参数供调用时使用。我们可以试着写一个乞丐版的 myBind 方法(bind 还能绑定参数,这个先略过了),这样可以更清晰地看到什么是返回函数的高阶函数了。

Function.prototype.myBind = function(context) {
  let func = this; // method is attached to the prototype, so just refer to it as this.
  return function newFn() {
    return func.apply(context, arguments);
  }
}

这里给 Function 的原型链加了一个新的函数 myBind,并用到了闭包(在内存里保留了原始函数和目标this);之后,调用 myBind 返回一个新的函数,并且在该函数运行时调用原始函数,左后通过apply执行时绑定目标 this。看一下效果:

let helloOnoin = greeting.myBind({name: 'Onion'});

console.log(helloOnoin()); // Hello Onoin

我这里再写一个健壮一点的 bind 实现,大家自己体会一下,bind 是如何将前几个参数也绑定了的:

Function.prototype.bind = function(context, ...args) {
  let func = this;
  return function () {
    return func.call(context, ...args, ...arguments);
  }
}


函数柯里化

高阶函数还在一种叫柯里化的方法里大显身手。

在数学和计算机科学中,柯里化是一种将使用多个参数的函数转换成一系列使用一个参数的函数,并且返回接受余下的参数而且返回结果的新函数的技术

柯里化,通俗点说就是先给原始函数传入几个参数,它会生成一个新的函数,然后让新的函数去处理接下来的参数。我们先不去管 curry 的实现,看看柯里函数的用法。比如,实现一个 add 函数——简单的两数相加,正常的运行就是直接加两参数运行——add(1,2)。但是这里我们先给它做个柯里化处理,并产生了一个新的函数——curryingAdd。

function curry(fn) { ... }

function add(a, b) { return a+b; }

const curryingAdd = curry(add);

柯里化后的 curryingAdd,从普通函数变成了高阶函数:它支持一次传入一个参数(比如 10)并返回一个新的函数——addTen。我们运行addTen(1),它会记录之前已经传入的 10,并把 10 和 1 相加得到 11。是不是觉得很没用?哈哈,这说明你 FP 学的不够深。

const addTen = curryAdd(10);

console.log(addTen(1)); // 11
console.log(addTen(100)); // 110

柯里化的作用就是将普通函数转变成高阶函数,实现动态创建函数、延迟计算、参数复用等等作用。篇幅有限,我不做深入讲解了。它实现上,就是返回一个高阶函数,通过闭包把传入的参数保存起来。当传入的参数数量不足时,递归调用 bind 方法;数量足够时则立即执行函数。学习一下 javascript 的高阶用法还是有意义的。

function curry(fn) {
  const arity = fn.length;

  return function $curry(...args) {
    if( args.length < arity ) {
      return $curry.bind(null, ...args);
    }
    return fn.apply(null, args);
  }
}


compose

compose 也是一个高阶函数里重要的一课。compose 就是组合函数,将子函数串联起来执行,一个函数的输出结果是另一个函数的输入参数,一旦第一个函数开始执行,会像多米诺骨牌一样推导执行后续函数。还是举个例子:我实现了一个带 Hello 的greeting函数,并希望在greeting调用结束后把返回值都显示成大写状态。

const greeting = name => `Hello ${name}`;
const toUpper = str => str.toUpperCase();

toUpper(greeting('Onion')); // HELLO ONION

传统的手段就是嵌套两个函数使用——toUpper(greeting('Onion')),但是有时候这种嵌套可能会很多,比如下面这个态势:

f(g(h(i(j(k('Onion'))))))

再看看 compose 的用法:

const composedFn = compose(f, g, h, i, j, k)
console.log( composedFn('Onion') )

是不是这一个 composedFn 函数比那种一层层的嵌套要美观的多?OK,怎么实现 compose 函数呢?把源码贴在这里了。如果你觉得写(...fns) => (...args) => ..这类代码不可思议的话,建议吭一下上面提到的教程《Mostly adequate guide》,吭完你就发现再正常不过了。

// compose: ( (a->b), (b->c), ..., (y->z) ) -> a -> z
const compose = (...fns) => (...args) => fns.reduceRight((res, fn) => [fn.apply(null, res)], args)[0];

原文:https://github.com/an-Onion/my-weekly/tree/master/037.%20JS 高阶函数

链接: https://www.fly63.com/article/detial/7333

JavaScript 函数式编程

我理解的 JavaScript 函数式编程,都认为属于函数式编程的范畴,只要他们是以函数作为主要载体的。

Js函数式编程,给你的代码增加一点点函数式编程的特性

给你的代码增加一点点函数式编程的特性,最近我对函数式编程非常感兴趣。这个概念让我着迷:应用数学来增强抽象性和强制纯粹性,以避免副作用,并实现代码的良好可复用性。同时,函数式编程非常复杂。

让我们来创建一个JavaScript Wait函数

Async/await以及它底层promises的应用正在猛烈地冲击着JS的世界。在大多数客户端和JS服务端平台的支持下,回调编程已经成为过去的事情。当然,基于回调的编程很丑陋的。

JavaScript函数创建的细节

如果你曾经了解或编写过JavaScript,你可能已经注意到定义函数的方法有两种。即便是对编程语言有更多经验的人也很难理解这些差异。在这篇博客的第一部分,我们将深入探讨函数声明和函数表达式之间的差异。

编写小而美函数的艺术

随着软件应用的复杂度不断上升,为了确保应用稳定且易拓展,代码质量就变的越来越重要。不幸的是,包括我在内的几乎每个开发者在职业生涯中都会面对质量很差的代码。这些代码通常有以下特征:

javascript回调函数的理解和使用方法(callback)

在js开发中,程序代码是从上而下一条线执行的,但有时候我们需要等待一个操作结束后,再进行下一步操作,这个时候就需要用到回调函数。 在js中,函数也是对象,确切地说:函数是用Function()构造函数创建的Function对象。

js调用函数的几种方法_ES5/ES6的函数调用方式

这篇文章主要介绍ES5中函数的4种调用,在ES5中函数内容的this指向和调用方法有关。以及ES6中函数的调用,使用箭头函数,其中箭头函数的this是和定义时有关和调用无关。

JavaScript中函数的三种定义方法

函数的三种定义方法分别是:函数定义语句、函数直接量表达式和Function()构造函数的方法,下面依次介绍这几种方法具体怎么实现,在实际编程中,Function()构造函数很少用到,前两中定义方法使用比较普遍。

js在excel的编写_excel支持使用JavaScript自定义函数编写

微软 称excel就实现面向开发者的功能,也就是说我们不仅可以全新定义的公式,还可以重新定义excel的内置函数,现在Excel自定义函数增加了使用 JavaScript 编写的支持,下面就简单介绍下如何使用js来编写excel自定义函数。

js中的立即执行函数的写法,立即执行函数作用是什么?

这篇文章主要讲解:js立即执行函数是什么?js使用立即执行函数有什么作用呢?js立即执行函数的写法有哪些?

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!