JavaScript 函数式编程 -- 划重点了!!!

函数是一等公民

在谈到函数式编程的时候,不少时候会听到这样一句话 "函数是一等公民"。那咱们如何去理解这句话呢? 编程

"一等" 这个术语一般用来描述值。因此当咱们说 "函数是一等公民" 时,也就是说函数拥有值的一切特性,你能够像看待一个值同样来看待一个函数。举个例子,数字在 JavaScript 中是一等公民,那么数字拥有的特性,也一样被函数所拥有。数组

  • 函数能够像数字同样被存储为一个变量
const num = 10;
const fun = function() {
    return 10;
}
  • 函数能够像数字同样做为数组的一个元素
const a = [10, function() { return 20; } ]
  • 函数能够像数字同样存在于对象的插槽里
const b = {
    name: 'Tony',
    age: function() { 
        return 20; 
    }
}
  • 函数能够像数字同样在使用时直接建立出来
10 + (function() { return 20; })(); // 30
  • 函数能够像数字同样被另外一个函数返回
const well = function() {
    return 10;
}

const good = function() {
    return function() {
        return 10;
    };
}
  • 函数能够像数字同样被传递给另外一个函数
const fun = function(value) { 
    return value; 
}

const happy = function(func) {
    return func(5) * 10;
}

happy(fun); // 50

最后两条其实就是 高阶函数 的定义,若是你如今不理解也没有关系,咱们在后面的部分会讲到它。闭包

变量做用域和闭包

变量做用域

变量的做用域和闭包做为 JavaScript 的基础,在学习函数式编程中是很是重要的,只有理解了它们,你才能更好的去理解咱们后面要讲到的高阶函数和部分应用等。app

关于变量的做用域,你须要知道:函数式编程

  • 全局做用域: JavaScript 中拥有最长生命周期 (一个变量的多长的时间内保持必定的值) 的变量,其变量的生命周期将跨越整个程序。
globalVariable = 'This is a global variable!';
  • 词法做用域: 词法做用域其实就是指一个变量的可见性,以及它文本表述的模拟值。
a = 'outter a';

function good() {
    a = 'middle a';
    return function() {
        a = 'inner a';
        return 'I am ' + a;
    }
}

good(); // I am inner a
PS:这里的示例代码仅仅是为了学习,你最好不要这样去写,由于它会让你的代码变得使人费解。

在上面的例子中,咱们分别对 a 变量进行了三次赋值,那么为何最后咱们拿到 a 的值是 'inner a' 而非其余呢?函数

当咱们声明 a = 'outter a' 时,程序会在栈中开辟一个空间去存储 a,当执行 good()函数时,咱们声明了 a = 'middle a',这时候会将栈中 a 的值修改掉,变成 'middle a',最后在执行 return 语句时,咱们又声明了 a = 'inner a',这时候会再次修改栈中的 a 的值,变成 'inner a'。所以获得了上面的结果。学习

在屡次给同一变量赋值时,最后获得的值是离使用时最近的一次赋值。经过查找离使用时最近的一次赋值,咱们能够快速的得出最后的结果。this

  • 动态做用域

提到 JavaScript 的动态做用域,就不得不提到 this 了。this 相关的知识不少,以后有时间再详细来说讲。如今咱们先记住 this 所指向的值由调用者肯定,以下代码所示:code

function globalThis() { return this; }

globalThis.call('APPLE'); //=> 'APPLE'
globalThis.call('ORANGE'); //=> 'ORANGE'
  • 函数做用域

闭包

提及闭包,不少人都会以为有点头疼,这的确是一个使人费解的概念,不过不要怕,它其实没有那么难以理解。对象

闭包的定义

闭包是一个函数和声明该函数的词法环境的组合

换句话说,闭包就是在使用时被做用域封闭的变量和函数。闭包能够捕获函数的参数和变量。

举个例子:

const fun = function() {
    const a = 10;
    return function(b) {
         return a + b;
    }
}
const myFunc = fun(); // 此时 myFunc 就变成一个闭包了,这个闭包能够捕获 fun 函数里的 a 变量,b 参数。

注意闭包是在使用时才会生成的,而非建立时。如上面的例子,若是只建立 fun 函数,而不执行最后一句 fun(),那么 fun 并不能称之为一个闭包。这里的闭包应该是 fun 运行时所产生的做用域,这个做用域捕获了fun 里面的变量和参数。

闭包的特色

  • 闭包会捕获一个值(或引用),并屡次返回相同的值
  • 每个新的闭包都会捕获不同的值

再来看一个例子:

const fun = function() {
    return function() {
        return 10;
    }
}

const myFunc = fun(); // myFunc 不是一个闭包

const fun2 = function(value) {
    return function() {
        return value;
    }
}
const myFunc2 = fun2('AWESOME'); // myFunc2 是一个闭包
myFunc2(); // AWESOME
myFunc2(); // AWESOME 屡次执行 myFunc2 闭包,返回的值相同

const myFunc3 = fun2('HAPPY'); // myFunc3 是一个新的闭包
myFunc3(); // HAPPY

这里 myFunc 严格意义上并不能叫做一个闭包,由于它并无捕获 fun 任何的变量或者是函数的传参。而 myFunc2 是一个闭包,由于它捕获了 fun2 的传参。

闭包的销毁

闭包延续了变量的生命周期,若是不手动销毁,闭包里面的变量会一直存在内存中。好比当咱们手动将 myFunc = null 时,闭包里面的变量才会被垃圾回收。

实用的闭包

说了这么多,你可能会有这样的疑问,闭包真的有用吗?闭包通常都会用到什么地方?

*1. 用闭包模拟私有方法, 使公共函数可以访问私有函数和变量,实现数据的隐藏和封装。私有方法有利于限制对代码的访问,而且提供了强大的管理命名空间的能力,避免了非核心代码对公共接口的干扰。

const Counter = () => {
    let count = 0;
    
    const change = (a) => {
        count = count + a;
    }
    
    return {
        increase: () => {
            change(1);
        },
        decrease: () => {
            change(- 1);
        },
        value: () => {
            return count;
        }
    }
}

const func1 = new Counter();
func1.value();  // 0

func1.increase(); 
func1.value(); // 1

func1.decrease();
func1.value(); // 0

*2. 经过一个高阶函数,生成不一样的闭包,从而获得多个保存不一样环境的新函数。

以下面的例子:

const makeAdder = function(x) {
    return function(y) {
        return x + y;
    }
}

const add5 = makeAdder(5);
const add10 = makeAdder(10);

makeAdder 实际上是一个函数工厂,用于建立将制定的值和它的参数求和的函数。经过它咱们又建立了两个新函数 add5 和 add10。add5 和 add10 都是闭包。它们共享着相同的函数定义,可是却保存了不一样的环境。在 add5 的环境中,x 为 5,可是在 add10 中,x 则为10。

高阶函数

定义

知足如下任意条件之一便可称之为高阶函数:

  • 以一个或者多个函数做为参数
  • 以一个函数做为返回结果

咱们常见的 map,find,reduce 都是以函数做为入参的函数,因此它们都是高阶函数。

以函数做为参数的函数

使用函数做为函数的参数,可让咱们建立出更灵活的函数。经过将参数从值替换为函数,咱们能够获得更多的可能性。由于在调用的时候,咱们能够经过传入不一样的函数来完成不一样的需求。

正以下面的例子:

const finder = function(val, func) {
    return val.reduce(function(prev, current) {
        return func(prev, current);
    });
}

const a = [1, 2, 3, 5, 8];
finder(a, Math.max); // 8
finder(a, Math.min); // 1

在使用 finder 函数时,经过传入不一样的函数,最后获得了彻底不一样的结果。这也是为何咱们强调 "使用函数,而不是值" 的缘由。

以函数做为返回结果的函数

以函数做为返回结果的函数,能够构建强大的函数。还记得咱们前面提到的闭包吗? 经过高阶函数 makeAdder,咱们生成了 add5 和 add10 两个新的函数。可以生成闭包的函数,其实都是高阶函数。

到这里,第一部分重点内容就讲完了。在下一部分中,咱们会讲到函数式编程中剩下的几个重要部分:

  • 柯里化和组合
  • 部分应用
  • 递归
  • 基于流的编程
相关文章
相关标签/搜索