阅读更多

1顶
0踩

Web前端

转载新闻 我眼中的 JavaScript 函数式编程

2017-03-22 14:14 by 副主编 jihong10102006 评论(0) 有6959人浏览

JavaScript 函数式编程是一个存在了很久的话题,但似乎从 2016 年开始,它变得越来越火热。这可能是因为 ES6 语法对于函数式编程更为友好,也可能是因为诸如 RxJS (ReactiveX) 等函数式框架的流行。

看过许多关于函数式编程的讲解,但是其中大部分是停留在理论层面,还有一些是仅针对 Haskell 等纯函数式编程语言的。而本文旨在聊一聊我眼中的函数式编程在 JavaScript 中的具体实践,之所以是 “我眼中的” 即我所说的仅代表个人观点,可能和部分 严格概念 是有冲突的。

本文将略去一大堆形式化的概念介绍,重点展示在 JavaScript 中到底什么是函数式的代码、函数式代码与一般写法有什么区别、函数式的代码能给我们带来什么好处以及常见的一些函数式模型都有哪些。

我理解的函数式编程
我认为函数式编程可以理解为,以函数作为主要载体的编程方式,用函数去拆解、抽象一般的表达式

与命令式相比,这样做的好处在哪?主要有以下几点:
  • 语义更加清晰
  • 可复用性更高
  • 可维护性更好
  • 作用域局限,副作用少
基本的函数式编程
下面例子是一个具体的函数式体现
// 数组中每个单词,首字母大写
// 一般写法
const arr = ['apple', 'pen', 'apple-pen'];
for(const i in arr){
  const c = arr[i][0];
  arr[i] = c.toUpperCase() + arr[i].slice(1);
}

console.log(arr);


// 函数式写法一
function upperFirst(word) {
  return word[0].toUpperCase() + word.slice(1);
}

function wordToUpperCase(arr) {
  return arr.map(upperFirst);
}

console.log(wordToUpperCase(['apple', 'pen', 'apple-pen']));


// 函数式写法二
console.log(arr.map(['apple', 'pen', 'apple-pen'], word => word[0].toUpperCase() + word.slice(1)));

当情况变得更加复杂时,表达式的写法会遇到几个问题:
  • 表意不明显,逐渐变得难以维护
  • 复用性差,会产生更多的代码量
  • 会产生很多中间变量
函数式编程很好的解决了上述问题。首先参看 函数式写法一,它利用了函数封装性将功能做拆解(粒度不唯一),并封装为不同的函数,而再利用组合的调用达到目的。这样做使得表意清晰,易于维护、复用以及扩展。其次利用 高阶函数,Array.map 代替 for…of 做数组遍历,减少了中间变量和操作。

函数式写法一 函数式写法二 之间的主要差别在于,可以考虑函数是否后续有复用的可能,如果没有,则后者更优。

链式优化
从上面 函数式写法二 中我们可以看出,函数式代码在写的过程中,很容易造成 横向延展,即产生多层嵌套,下面我们举个比较极端点的例子。
// 计算数字之和

// 一般写法
console.log(1 + 2 + 3 - 4)


// 函数式写法
function sum(a, b) {
  return a + b;
}

function sub(a, b) {
  return a - b;
}

console.log(sub(sum(sum(1, 2), 3), 4);

本例仅为展示 横向延展 的比较极端的情况,随着函数的嵌套层数不断增多,导致代码的可读性大幅下降,还很容易产生错误。

在这种情况下,我们可以考虑多种优化方式,比如下面的 链式优化
// 优化写法 (嗯,你没看错,这就是 lodash 的链式写法)
const utils = {
  chain(a) {
    this._temp = a;
    return this;
  },
  sum(b) {
    this._temp += b;
    return this;
  },
  sub(b) {
    this._temp -= b;
    return this;
  },
  value() {
    const _temp = this._temp;
    this._temp = undefined;
    return _temp;
  }
};

console.log(utils.chain(1).sum(2).sum(3).sub(4).value());

这样改写后,结构会整体变得比较清晰,而且链的每一环在做什么也可以很容易的展现出来。函数的嵌套和链式的对比还有一个很好的例子,那就是 回调函数 Promise 模式
// 顺序请求两个接口


// 回调函数
import $ from 'jquery';
$.post('a/url/to/target', (rs) => {
  if(rs){
    $.post('a/url/to/another/target', (rs2) => {
      if(rs2){
        $.post('a/url/to/third/target');
      }
    });
  }
});


// Promise
import request from 'catta';  // catta 是一个轻量级请求工具,支持 fetch,jsonp,ajax,无依赖
request('a/url/to/target')
  .then(rs => rs ? $.post('a/url/to/another/target') : Promise.reject())
  .then(rs2 => rs2 ? $.post('a/url/to/third/target') : Promise.reject());

随着回调函数嵌套层级和单层复杂度增加,它将会变得臃肿且难以维护,而 Promise 的链式结构,在高复杂度时,仍能纵向扩展,而且层次隔离很清晰。

常见的函数式编程模型
闭包(Closure)
引用
可以保留局部变量不被释放的代码块,被称为一个闭包

闭包的概念比较抽象,相信大家都或多或少知道、用到这个特性

那么闭包到底能给我们带来什么好处?

先来看一下如何创建一个闭包:
// 创建一个闭包
function makeCounter() {
  let k = 0;

  return function() {
    return ++k;
  };
}

const counter = makeCounter();

console.log(counter());  // 1
console.log(counter());  // 2

makeCounter 这个函数的代码块,在返回的函数中,对局部变量 k ,进行了引用,导致局部变量无法在函数执行结束后,被系统回收掉,从而产生了闭包。而这个闭包的作用就是,“保留住“ 了局部变量,使内层函数调用时,可以重复使用该变量;而不同于全局变量,该变量只能在函数内部被引用。

换句话说,闭包其实就是创造出了一些函数私有的 ”持久化变量“。

所以从这个例子,我们可以总结出,闭包的创造条件是:
  • 存在内、外两层函数
  • 内层函数对外层函数的局部变量进行了引用
闭包的用途
闭包的主要用途就是可以定义一些作用域局限的持久化变量,这些变量可以用来做缓存或者计算的中间量等等。
// 简单的缓存工具
// 匿名函数创造了一个闭包
const cache = (function() {
  const store = {};
  
  return {
    get(key) {
      return store[key];
    },
    set(key, val) {
      store[key] = val;
    }
  }
}());

cache.set('a', 1);
cache.get('a');  // 1

上面例子是一个简单的缓存工具的实现,匿名函数创造了一个闭包,使得 store 对象 ,一直可以被引用,不会被回收。

闭包的弊端
持久化变量不会被正常释放,持续占用内存空间,很容易造成内存浪费,所以一般需要一些额外手动的清理机制。

高阶函数
引用
接受或者返回一个函数的函数称为高阶函数

听上去很高冷的一个词汇,但是其实我们经常用到,只是原来不知道他们的名字而已。JavaScript 语言是原生支持高阶函数的,因为 JavaScript 的函数是一等公民,它既可以作为参数又可以作为另一个函数的返回值使用。

我们经常可以在 JavaScript 中见到许多原生的高阶函数,例如 Array.map , Array.reduce , Array.filter

下面以 map 为例,我们看看他是如何使用的

map (映射)
引用
映射是对集合而言的,即把集合的每一项都做相同的变换,产生一个新的集合

map 作为一个高阶函数,他接受一个函数参数作为映射的逻辑
// 数组中每一项加一,组成一个新数组

// 一般写法
const arr = [1,2,3];
const rs = [];
for(const n of arr){
  rs.push(++n);
}
console.log(rs)


// map改写
const arr = [1,2,3];
const rs = arr.map(n => ++n);

上面一般写法,利用 for...of 循环的方式遍历数组会产生额外的操作,而且有改变原数组的风险

而 map 函数封装了必要的操作,使我们仅需要关心映射逻辑的函数实现即可,减少了代码量,也降低了副作用产生的风险。

柯里化(Currying)
引用
给定一个函数的部分参数,生成一个接受其他参数的新函数

可能不常听到这个名词,但是用过 undescore 或 lodash 的人都见过他。

有一个神奇的 _.partial 函数,它就是柯里化的实现
// 获取目标文件对基础路径的相对路径


// 一般写法
const BASE = '/path/to/base';
const relativePath = path.relative(BASE, '/some/path');


// _.parical 改写
const BASE = '/path/to/base';
const relativeFromBase = _.partial(path.relative, BASE);

const relativePath = relativeFromBase('/some/path');

通过 _.partial ,我们得到了新的函数 relativeFromBase ,这个函数在调用时就相当于调用 path.relative ,并默认将第一个参数传入 BASE ,后续传入的参数顺序后置。

本例中,我们真正想完成的操作是每次获得相对于 BASE 的路径,而非相对于任何路径。柯里化可以使我们只关心函数的部分参数,使函数的用途更加清晰,调用更加简单。

组合(Composing)
引用
将多个函数的能力合并,创造一个新的函数

同样你第一次见到他可能还是在 lodash 中,compose 方法(现在叫 flow)
// 数组中每个单词大写,做 Base64


// 一般写法 (其中一种)
const arr = ['pen', 'apple', 'applypen'];
const rs = [];
for(const w of arr){
  rs.push(btoa(w.toUpperCase()));
}
console.log(rs);


// _.flow 改写
const arr = ['pen', 'apple', 'applypen'];
const upperAndBase64 = _.partialRight(_.map, _.flow(_.upperCase, btoa));
console.log(upperAndBase64(arr));

_.flow 将转大写和转 Base64 的函数的能力合并,生成一个新的函数。方便作为参数函数或后续复用。

自己的观点
我理解的 JavaScript 函数式编程,可能和许多传统概念不同。我并不只认为 高阶函数 算函数式编程,其他的诸如普通函数结合调用、链式结构等,我都认为属于函数式编程的范畴,只要他们是以函数作为主要载体的。

而我认为函数式编程并不是必须的,它也不应该是一个强制的规定或要求。与面向对象或其他思想一样,它也是其中一种方式。我们更多情况下,应该是几者的结合,而不是局限于概念。

参考资料
  • 大小: 370.2 KB
来自: 化辰
1
0
评论 共 0 条 请登录后发表评论

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • 深入解析Backbone.js框架的依赖库Underscore.js的作用

    注:backbone可以很好的与其它js库一起工作,所以说它是一个库,而不是框架。 Underscore并没有对原生对象进行扩展,而是调用_()方法进行封装,一旦封装完成,js对象就变为Underscore对象,也可以通过Underscore对象...

  • JavaScript框架有哪些?JS框架汇总

    JavaScript是一种多范式语言,支持事件驱动,功能和命令式(包括面向对象和基于原型的)编程样式。JavaScript最初仅用于客户端。但是,如今,JavaScript也被用作服务器端编程语言。...JavaScript框架是用Java

  • web前端技术分享:常用JavaScript框架有哪些?

    常用JavaScript框架有哪些?对于前端开发工作者来说,JavaScript绝对是绕不开的一个点,可以所前端的大部分动态交互都是由JS完成...下面小千就来为大家介绍一个常见并且实用的JavaScript框架,看看其中有没有你的偏爱。

  • javascript主要框架

    这确实是一个明确的库,在 three.js 的用例展示中,有几百个效果很好的例子。他的想法是直接抽取出他喜欢的angular的特性,不再引入其他复杂的理念而打造一款新的框架。它在javascript运行时环境中工作,因此显示了...

  • 前端_快速入门Vue.js框架

    文章目录快速入门Vue.js框架0、前言1、Vue.js框架1.1、Vue简介1.2、第一个Vue程序1.3、el:挂载点2、Vue...快速入门Vue.js框架 0、前言 前后端分离是现在很火的一个词,而实现前后端分离架构的技术栈之一呢就是“Spring

  • 如果没有JS框架该怎么办

    如果您考虑使用自己的框架,请注意有一组成本没有在本文中讨论。 普通的选择 web平台已经提供了一种开箱即用的声明式编程机制:HTML和CSS。这种机制是成熟的、经过良好测试的、流行的、广泛使用的和有文档记载的。...

  • Java/JavaScript有哪些图形图像处理的框架?

    Java图像库 JAI ImageJ JS 的图形框架 Javascript 的主流框架有antV,echarts,handsometable,D3,three.js,fabric等,两种绘图机制:基于svg,基于canvas和dom。 Canvas技术的诞生让绘图技术如虎添翼,使得JS能...

  • 常用Node.js 框架一览

    原文地址:Top Node.js Frameworks to use in 2021 原文作者:Ronak Patel 译文出自:掘金翻译计划 本文永久链接:...

  • JavaScript框架从入门到精通

    静态网页和动态网页 动态网页 我们有了html超文本标记语言实现了网站页面展现,展现文字、表格、图片、超链接等,有了css样式表实现了页面的美化,这些...这时javascript就派上用场了。它能实现浏览器用户和后台服务

  • 十大热门JavaScript框架汇总

    JavaScript是一种多范式语言,支持事件驱动,功能和命令式(包括面向对象和基于原型的)编程样式。JavaScript最初仅用于客户端...JavaScript框架是用JavaScript编写的应用程序框架,程序员可以在其中操纵功能并方便...

  • 几大主流的前端框架(UI/JS)框架

    前端至少要懂的三个部分:HTML,CSS,JavaScript(简称JS),那首先先明确这三个概念: HTML负责结构,网页想要表达的内容由html书写。 CSS负责样式,网页的美与丑由它来控制 JS负责交互,用户和网页产生的互动由它...

  • Java及JavaScript常见框架汇总

    Java的九大顶级框架: 顶级Java框架#1:Spring Spring排在第一位,是由于它能够开发以高性能著称的复杂web应用程序的出色能力。它能够使Java开发人员轻松地创建企业级应用程序。 Web应用程序开发人员可以担保...

  • 基于Three.js的全景展示框架-TPano

    以上这种方式可用于素材不多,发布后不会轻易变化,而且对制作时间没有精确时间要求的场景。而且应用多依赖于人工操作,如果想直接对采集的全景照片进行直接展示的,可以采用什么方案呢?本文将介绍一......

  • vue.js框架简单认识

    Vue.js是一款流行的JavaScript前端框架,一个用于创建用户界面的开源JavaScript框架,旨在更好地组织与简化Web开发。Vue所关注的核心是MVC模式中的视图层,同时,它也能方便地获取数据更新,并通过组件内部特定的...

  • 常用的前端框架有哪些?

    常用的前端框架有Bootstrap框架、React框架、Vue框架、Angular框架、Foundation框架等等 现在越来越多的前端框架开始出现,这为我们的项目需求带来了极大的方便。本文将为大家详细介绍几种前端框架,有一定的参考...

  • React 框架

    React 框架学习

  • JavaScript框架发展的四个时代,你经历过其中几个阶段?

    不同时期,在什么样的技术驱动下,其底层框架发生了革新?在本篇文章中,资深开发者Chris将带领我们领略JavaScript四个不同的时期。作者 |Chris ;译者|彭慧中; 责编 | 屠敏出品 | CSDN(ID:CSDNnews)早在2012年,....

  • 这10个热门的JavaScript框架,哪个适合你呢?

    多年来,业界已经发布了大量JavaScript框架,怎样进行选择可能是一个挑战。如果你感到困惑,不知道应该选哪个或者究竟哪个适合你,那么这篇文章可以帮你解决问题。在本文中,我将列出用来构建 Web 应用程序的前10个...

  • 2022 年 JavaScript 开发工具的生态,别再用过时的框架了

    从上面来看,使用swc或esbuild作为...现代Web应用开发在近几年有了飞速发展,在工具领域,如今我们有很多很多选择,希望这篇文章能对你未来的技术选型有所帮助。httpshttpshttpshttpshttpshttpshttpshttpshttps。...

  • setting.xml文件,修改Maven仓库指向至阿里仓

    setting.xml文件,修改Maven仓库指向至阿里仓

Global site tag (gtag.js) - Google Analytics