函数式编程

JavaScript ES2015:模块 Module

你可以创建 JavaScript 模块,在模块里你可以导出需要导出的东西,然后在其它的地方,你可以导入模块提供的东西。

创建模块

创建一个模块,可以放在一个单独的 js 文件里,比如 talk.js:

let greeting = 'hello'

export { greeting }

现在,talk.js 就是一个模块,导出模块里的东西使用的是 export ,这里我们导出来变量 greeting 。

使用模块

使用模块就是导入模块提供的东西,创建一个 js 文件,名字是 index.js,在这个文件里:

import { greeting } from './talk'

console.log(greeting)

执行上面的代码,会在控制台上输出 “hello”,这个值是在 talk 模块里导出的 greeting 表示的东西。在 index.js 里,我们用 import 导入了 talk 模块里导出的 greeting 。

JavaScript ES2015(ES6):准备使用 ES2015

你写了几行 JavaScript 代码,放到浏览器上就能去运行,这是因为浏览器里面都有一个处理 JavaScript 语言的引擎。不同的浏览器使用的引擎可能不太一样,比如 Chrome 浏览器用的就是 Google 研究出来的 JavaScript 引擎。这套引擎不仅被放到了 Chrome 浏览器里,还可以被放在服务器上,也就是 Node.js。

因为处理 JavaScript 用的引擎不仅是只有 Google 的,还有其它的公司或者组织做的 JavaScript 引擎,这就需要大家坐在一起商量出一套规范。不然我们写的 JavaScript 代码,在不同环境下的运行结果可能就不一致了,我们可能要分别为 Chrome,IE,Firefox 各自去写一套 JavaScript 代码。对于我们开发者来说,谁都不喜欢这样。我们想要的是自己的 JavaScript 代码在任何支持运行 JavaScript 的环境下的运行结果都是一样的。

JavaScript Functional Programming:箭头函数 Arrow functions

箭头函数在 JavaScript 里面,是 ES6(ES2015)才加入进来的。因为函数里有个像箭头一样的符号:=>,所以叫箭头函数,英文经常也会称为 Fat arrow functions,胖乎乎的箭头函数。这种函数也称为 lambda 表达式。箭头函数不能当作构造函数使用。

语法

一个箭头函数看起来像这样:

const greet = () => hello

箭头(=>)左边是函数的参数,如果函数没有参数,要使用一组空白的括号,如果函数只有一个参数,这个参数的周围可以不用括号,如果有多少参数,这些参数要放在一组括号里,中间用逗号分隔开。

箭头右边是函数的主体部分,上面这个函数的主体只有一行,所以可以直接把主体放在箭头的右边,箭头函数会自动返回这种单行的主体的结果,也就是你不需要明确的使用 return 关键词返回值。如果函数的主体是多行的,可以把主体部分放在一组大括号里({  })。

单个参数

const greet = name => `hello, ${name}`

name 是 greet 这个箭头函数的一个参数,因为函数只有一个参数,所以它的周围不需要添加括号。

JavaScript Functional Programming:组合函数 Composition

组合(Composition)函数,就是把两个或以上的函数组合到一块儿,整成一个新的函数。我找到了一个很好的例子,很好地解释了组合函数这个概念。

比如一个应用主要是记录一下日常的花销(expenses),应用里的数据看起来像这样:

const expenses = [
  {
    name: '租金',
    price: 3000,
    type: '日常'
  }, 
  {
    name: '阿里云服务',
    price: 600,
    type: '服务'
  }, 
  {
    name: '健身中心',
    price: 50,
    type: '健康'
  }, 
  {
    name: '水电',
    price: 100,
    type: '日常'
  }
];

合计花销

现在我要合计一下所有花销,创建一个函数,用一下 map 与 reduce,这个函数像这样:

const sum = (source) => 
  source
    .map((item) => item.price)
    .reduce((accumulator, price) => accumulator + price, 0)

sum 这个函数接收一个 source 参数,在函数里,先用 map,返回 source 里的所有的 price(价格)。然后再用 reduce 去处理返回的 price ,这里就是合计所有的 price 的值。

这个函数用起来像这样:

let total = sum(expenses) // 结果:3750

JavaScript Functional Programming:不变性 Immutability

不变性(immutability),指的是一个东西被创建以后就不会发生变化了。函数式编程里的东西一般都具有这种特性。让对象具有不变性是一件好事,如果对象需要发生改变,你应该去创建一个新的对象,在新的对象里包含发生改变的部分,不要直接去修改对象本身。

下面这几个单词经常会出现:

  1. immutability:名词,不变性。表示东西不会发生改变的这种特性。
  2. immutable:形容词,不可变。可以说一个东西 immutable ,表示这个东西不会发生变化。
  3. mutability:名词,可变性,突变性,易变性。表示东西会发生改变的这种特性。
  4. mutate:动词,突变。
  5. mutable:形容词,可变。

在 JavaScript 里面,字符串与数字都具有不变性。也就是一个字符串一旦被创建,它的值就不会发生变化。但是 JavaScript 里的数组或对象是可变的,让它们不可变可以使用 Object.freeze 冻结一下它们(只能冻一层),也可以使用一些外部库,比如 Immutable.js。

实验一下:

let name = 'ninghao.net'
undefined

name[7]
"."

name[7] = '-'
"-"

name
"ninghao.net"

在上面尝试修改 name 的值(把 ninghao.net 里的点“ . ” 换成 “ - ” ),办不到。因为字符串这种值不能被改变。

JavaScript Functional Programming:Currying

Currying 指的就是把一个接受多个参数的函数,搞成每次只接收一个参数的函数序列。

看个例子:

const greet = (greeting, name) => {
  return `${greeting}, ${name}`
}

greet('hello', 'ninghao') // “hello, ninghao”

上面的 greet 就是一个接收多个参数的函数。如果把它转换成 Currying 风格的函数,会像这样:

const greet = greeting => name => `${greeting}, ${name}`

上面用了箭头函数,如果写成普通的函数应该像这样:

JavaScript Functional Programming:高阶函数 Higher order functions

高阶函数(higher-order functions),就是返回其它函数的函数,或者使用其它函数作为它的参数的函数。

使用函数作为参数

因为函数本身就是一个值,所以可以让函数作为参数传递给其它的函数。JavaScript 有些函数就需要用到函数类型的参数,比如 Array.map。

比如我有一组数据:

const names = ['小猫', '小狗', '小刺猬']

我要分别问候一下这组数据里的每个项目:

const greetings = names.map(function(name) {
  return `hi ~ ${name}`
})

console.log(greetings)
// ["hi ~ 小猫", "hi ~ 小狗", "hi ~ 小刺猬"]

上面的 map 方法里用了一个匿名函数作为它的参数。在这个函数里面,我们在数组里的每个项目的前面都加上了一个 “hi ~” ,map 会返回一个新的数组,这个数组我交给了 greetings 变量。

JavaScript Functional Programming:声明式与命令式

函数式编程属于声明式编程(declarative programming)的范畴,经常跟声明式编程一块儿讨论的是命令式编程(imperative programming),因为它们是两种不太一样的风格。

命令式编程一般就是说清楚具体要怎么样得到一个结果:先这样做,再这样做,然后再这样,如果这样,就这样做 ...  声明式编程就是声明(说明)一下你想得到的结果是什么样的:把这组电影里的平均分大于 9 分的电影过滤出来给我。

比如有一组电影,你想过滤出评分 9 分以上的电影。

let movies = [
  { title: 'The Shawshank Redemption', rating: 9.6 },
  { title: 'Forrest Gump', rating: 9.4 },
  { title: 'Roman Holiday', rating: 8.9 }
]

JavaScript Functional Programming:纯函数

函数式编程鼓励我们多创建纯函数(pure functions),纯函数只依赖你交给它的东西,不使用任何函数以外的东西,也不会影响到函数以外的东西。跟纯函数对应的就是不纯函数(impure functions),也就是不纯函数可能会使用函数以外的东西,比如使用了一个全局变量。也可能会影响到函数以外的东西,比如改变了一个全局变量的值。

多使用纯属函数是因为它更可靠一些,也没什么副作用(side effects)。你交给它同样的值,它每次都会给你输出同样的结果,这种特质叫所指透明(Referential transparency) 。这会让程序更稳定,也更容易测试。

副作用

纯函数没副作用,有副作用的函数都不纯。我吃了一片感冒药,是要治我的感冒,但副作用是它让我想睡觉。函数的副作用多数表现为函数依赖或者改变了它以外的东西。

看个例子:

let name = 'ninghao'
const greet = () => {
  console.log(`hello, ${name}`)
}

greet 不是纯函数,因为这个函数依赖函数以外的东西,这里就是全局作用域下的 name。这样做的问题是,函数依赖的 name 很可能在应用运行的时候发生变化,这样试一下:

JavaScript Functional Programming:作用域

全局作用域

打开一个 js 文件,写了一行代码,这行代码所在的位置就会是全局作用域(global scope)。比如:

var name = '狗狗'

局部作用域

全局作用域只有一个,在全局使用域里面定义的其它的作用域都被称为局部作用域(local scope)。局部作用域是由函数创建的,每个函数都会创建一个局部作用域。

下面我创建了一个名字是 greet 的函数,在它里面声明了一个 name 变量,这个变量是在局部作用域之内。

// 作用域 A: 全局作用域

var greet = function() {
  // 作用域 B:局部作用域
  // 这里是由 greet 这个函数创建的局部作用域

  var name = '猫咪'
}

在这个局部作用域里面定义的东西,在这个作用域的外面是访问不到的。试一下:

统计

14696
分钟
0
你学会了
0%
完成

社会化网络

关于

微信订阅号

扫描微信二维码关注宁皓网,每天进步一点