函数式编程

411 阅读7分钟

1 定义

1. 函数:是一段可以通过其名称被调用的代码,他可以传递参数并返回只=值。

2. 方法:是一段必须通过其名称及其关联对象的名称被调用的代码。

// 一个简单的函数
var simple = (a) => {return a}; 
simple(5); // 用其名称调用

// 一个简单的方法
var obj = {
  simple: (a) => {return a}
}
obj.simple(5) // 用其名称及其关联对象调用

3. 引用透明性:所有的函数对于相同的输入都将返回相同的值。

4. 纯函数:对于给定的输入返回相同的输出。

5. javascript 是函数式编程语言。

6. 高阶函数:接受函数作为参数并且/或者返回函数作为输出的函数。


2 高阶函数

2.1 every 函数

对数组中的每个元素都执行一次指定的函数(callback)。如果回调函数对每个元素执行后都返回 true ,every 将返回 true,否则返回false。它只对数组中的非空元素执行指定的函数,没有赋值或者已经删除的元素将被忽略。

[NaN,NaN,NaN].every(isNaN)   // true
[NaN,NaN,4].every(isNaN)    // false


2.2 some 函数

对数组中的每个元素都执行一次指定的函数(callback),直到此函数返回 true,如果发现这个元素,some 将返回 true,如果回调函数对每个元素执行后都返回 false ,some 将返回 false。它只对数组中的非空元素执行指定的函数,没有赋值或者已经删除的元素将被忽略。

[NaN,NaN,NaN].every(isNaN)   // true
[3,4,4].every(isNaN)    // false


2.3 sort 函数

arrayObject.sort(sortby)

如果调用该方法时没有使用参数,将按字母顺序对数组中的元素进行排序,说得更精确点,是按照字符编码的顺序进行排序。要实现这一点,首先应把数组的元素都转换成字符串(如有必要),以便进行比较。

如果想按照其他标准进行排序,就需要提供比较函数,该函数要比较两个值,然后返回一个用于说明这两个值的相对顺序的数字。比较函数应该具有两个参数 a 和 b,其返回值如下:

  • 若 a 小于 b,在排序后的数组中 a 应该出现在 b 之前,则返回一个小于 0 的值。
  • 若 a 等于 b,则返回 0。
  • 若 a 大于 b,则返回一个大于 0 的值。

[9,1,11].sort()  // [1, 11, 9]
[9,1,11].sort((a,b)=>{return a-b;})  // [1, 9, 11]


3 闭包

1. 闭包:闭包就是一个内部函数。

2. 内部函数:在另一个函数内部的函数。

3. 闭包的作用域:

  • 可以访问在它自身声明之内声明的变量;

function outer() {
  function inner(){
    let a = 5;
    console.log(a); // 5
  }
  inner();
}

  • 对全局变量的访问;

let global = 'global';
function outer() {
  function inner(){
    let a = 5;
    console.log(global);
  }
  inner();  // global
}

  • 可以访问外部函数变量的访问。

let global = 'global';
function outer() {
  let outer = 'outer';
  function inner(){
    let a = 5;
    console.log(outer);
  }
  inner();  // outer
}

4. 闭包可以记住它的上下文

var fn = (arg) => {
    let outer = 'visible';
    let innerFn = () => {
        console.log(outer);
        console.log(arg)
    }
    return innerFn;
}

outer = 'global'
var closureFn = fn(5);
closureFn();  // visible  5

当 innerFn 被返回时,innerFn被视为闭包,并相应地设置了它的作用域,所以,返回函数的引用存储在 closureFn 中。


4 数组的函数式编程

4.1 map函数

map 函数返回了给定函数转换后的值,故也称为投影函数或转换函数。

[1,2,3].map(parseInt);    //  [1, NaN, NaN]

原因:map 接受三个参数 element,index,arr;parseInt 接受两个参数 parse,radix。

如果把 parseInt 传给 map,map函数会把 index 的值传给 parseInt 的 radix。

解决方法:

const unary = (fn) => {
    return fn.length === 1 ? fn : (arg) => fn(arg)
}
[1,2,3].map(unary(parseInt));    // [1,2,3]


4.2 filter 函数

对数组中的每个元素都执行一次指定的函数(callback),并且创建一个新的数组,该数组元素是所有回调函数执行时返回值为 true 的原数组元素。它只对数组中的非空元素执行指定的函数,没有赋值或者已经删除的元素将被忽略,同时,新创建的数组也不会包含这些元素。

回调函数可以有三个参数:当前元素,当前元素的索引和当前的数组对象。

[1,2,3,4,5].filter((element)=>{
    return element > 3
})
// [4, 5]


4.3 concat

用于连接两个或多个数组。

该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本。

[1,2,3].concat([5,6,7]);    // [1, 2, 3, 5, 6, 7]


4.4 reduce

接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值将。即数组元素计算为一个值(从左到右)

array.reduce(function(total, currentValue, currentIndex, arr), initialValue)

total 必需。初始值, 或者计算结束后的返回值。 

currentValue 必需。当前元素。

currentIndex 可选。元素的索引。

arr 可选。当前元素所属的数组对象。 

initialValue 可选。传递给函数的初始值。

[1,2,3,4].reduce((total,currentValue)=>{return total+currentValue;}) // 10
[1,2,3,4].reduce((total,currentValue)=>{return total*currentValue;},0);    // 0
[1,2,3,4].reduce((total,currentValue)=>{return total*currentValue;},1);    // 24

4.5 reduceRoght

将数组元素计算为一个值(从右到左)。 


5 Generator

5.1 基础知识

function* gen(){
    return 'first generator'
}
let genResult = gen();
console.log(genResult);
// gen{[[GeneratorStatus]]: "suspended",[[GeneratorReceiver]]: Window}

上面结果说明了genResult 不是一个普通的函数,而是一个 Generator 原始类型的实例。

如何从该 Generator 实例中获取值?

答案:调用该实例的 next 函数。

console.log(genResult.next());  // {value: "first generator", done: true}

故,用以下方法来获取值: 

console.log(genResult.next().value); // first generator


5.2 yield 关键字

function* genSequenceFun() {    yield 'first';    yield 'second';    yield 'third'}let genSequence = genSequenceFun();console.log(genSequence.next().value);  // firstconsole.log(genSequence.next().value);  // secondconsole.log(genSequence.next().value);  // third

yield 使 Generator 函数暂停了执行并将结果返回给调用者。因此,当第一次调用 genSequenceFun 时,函数看到了 yield 后面的值是first,yield 将函数置于暂停模版并返回了该值(而且它准确地记住了暂停的位置)。当下一次调用 genSequenceFun 时(使用相同的实例变量),Generator 函数将从它中断的地方回复执行。


5.3 done属性

每次对 next 函数的调用都将返回一个如下所示的对象:

[value: 'value', done: false]

done 它是一个判断 Generator 序列是否已被完全消费的属性。

function* genSequenceFun() {    yield 'first';    yield 'second';    yield 'third'}let genSequence = genSequenceFun();console.log(genSequence.next());  // {value: "first", done: false}console.log(genSequence.next());  // {value: "second", done: false}console.log(genSequence.next());  // {value: "third", done: false}console.log(genSequence.next());  // {value: undefined, done: true}

可见, done 属性清楚的告诉我们 Generator 序列已被完全消费了,所以,当 done = true 时,就应该停止调用 Generator 的实例 next


5.4 注意事项

  • 不能无限制地调用 next 从 Generator 中取值。

function* gen() {    
    return 'first generator'
}
let genResult = gen();
console.log(genResult.next().value);  // first generator
console.log(genResult.next().value);  // undefined

原因:Generator 如同序列,一旦序列中的值被消费,你就不能再次消费它。

为了能够再次消费该序列,需要创建另一个 Generator 实例:

function* gen() {    
    return 'first generator'
}
// 第一个序列let 
genResult1 = gen();
console.log(genResult1.next().value); // first generator
// 第二个序列let 
genResult2 = gen();
console.log(genResult2.next().value); // first generator

  • 所有带 yield 的 Generator 都会以惰性求值的顺序执行。

惰性求值,即代码指导调用时才会执行,当我们需要的时候,相应的值才会被计算并返回。

  • for 循环:利用了 Generator 的 done 属性进行遍历。

function* genSequenceFun() {    yield 'first';    yield 'second';    yield 'third'}let genSequence = genSequenceFun();for (let value of genSequence) {    console.log(value);}
// first,second,third


5.5 向 Generator 传递数据

function* sayFullName() {    let firstName = yield;    let secondName = yield;    console.log(firstName + secondName);}let fullName = sayFullName();fullName.next();fullName.next('Huang');fullName.next('Xiaoming');
// HuangXiaoming

当第一次调用 next 时,代码将暂定并返回于此:let firstName = yield;

由于没有通过 yield 发送任何值,因此 next 返回 undefined

第二次调用 next 时,传递数据 Huang,Generator 将从上一次暂定的状态(let firstName = yield;)中恢复,并将 yield 替换成 Huang,firstName 被赋值后,执行将恢复,直到遇见 yield 再次暂停。以此类推。