摘要:函数是一等公民在谈到函数式编程的时候,很多时候会听到这样一句话函数是一等公民。变量作用域和闭包变量作用域变量的作用域和闭包作为的基础,在学习函数式编程中是非常重要的,只有理解了它们,你才能更好的去理解我们后面要讲到的高阶函数和部分应用等。
函数是一等公民
在谈到函数式编程的时候,很多时候会听到这样一句话 "函数是一等公民"。那我们如何去理解这句话呢?
"一等" 这个术语通常用来描述值。所以当我们说 "函数是一等公民" 时,也就是说函数拥有值的一切特性,你可以像看待一个值一样来看待一个函数。举个例子,数字在 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 的基础,在学习函数式编程中是非常重要的,只有理解了它们,你才能更好的去理解我们后面要讲到的高阶函数和部分应用等。
关于变量的作用域,你需要知道:
全局作用域: 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"。因此得到了上面的结果。
在多次给同一变量赋值时,最后得到的值是离使用时最近的一次赋值。通过查找离使用时最近的一次赋值,我们可以快速的得出最后的结果。
动态作用域
提到 JavaScript 的动态作用域,就不得不提到 this 了。this 相关的知识很多,之后有时间再详细来讲讲。现在我们先记住 this 所指向的值由调用者确定,如下代码所示:
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 两个新的函数。能够生成闭包的函数,其实都是高阶函数。
到这里,第一部分重点内容就讲完了。在下一部分中,我们会讲到函数式编程中剩下的几个重要部分:
柯里化和组合
部分应用
递归
基于流的编程
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/90738.html
摘要:本文是响应式编程第二章序列的深入研究这篇文章的学习笔记。函数科里化的基本应用,也是函数式编程中运算管道构建的基本方法。四资料参考函数式编程指南 本文是Rxjs 响应式编程-第二章:序列的深入研究这篇文章的学习笔记。示例代码托管在:http://www.github.com/dashnowords/blogs 更多博文:《大史住在大前端》目录 showImg(https://segme...
摘要:想学好前端,真的要主动,然后对所有的英文文档耐心一点。在年月日,国际组织发布了的第六版,该版本正式名称为,但通常被称为或者。自此,每年发布一次新标准。但保留了用于依赖注入的构造函数参数类型。必须在构造函数中声明属性,而不是在类的代码体中。 从 TypeScript 到 ES6 到 ES5 在我初学前端的很长一段时间,不愿意碰git,不愿意碰框架,总是嫌麻烦,连ES6也没有怎么去弄明白...
摘要:本文是响应式编程第一章响应式这篇文章的学习笔记。通过代码对比可以发现,在响应式编程中,我们不再用对象的概念来对现实世界进行建模,而是使用流的思想对信息进行拆分和聚合。 本文是Rxjs 响应式编程-第一章:响应式这篇文章的学习笔记。示例代码地址:【示例代码】 更多文章:【《大史住在大前端》博文集目录】 showImg(https://segmentfault.com/img/bVbuE...
摘要:本文是响应式编程第四章构建完整的应用程序这篇文章的学习笔记。涉及的运算符每隔指定时间将流中的数据以数组形式推送出去。中提供了一种叫做异步管道的模板语法,可以直接在的微语法中使用可观测对象示例五一点建议一定要好好读官方文档。 本文是【Rxjs 响应式编程-第四章 构建完整的Web应用程序】这篇文章的学习笔记。示例代码托管在:http://www.github.com/dashnoword...
摘要:函数式编程前端掘金引言面向对象编程一直以来都是中的主导范式。函数式编程是一种强调减少对程序外部状态产生改变的方式。 JavaScript 函数式编程 - 前端 - 掘金引言 面向对象编程一直以来都是JavaScript中的主导范式。JavaScript作为一门多范式编程语言,然而,近几年,函数式编程越来越多得受到开发者的青睐。函数式编程是一种强调减少对程序外部状态产生改变的方式。因此,...
阅读 1762·2023-04-26 02:14
阅读 3690·2021-11-23 09:51
阅读 1324·2021-10-13 09:39
阅读 3943·2021-09-24 10:36
阅读 2947·2021-09-22 15:55
阅读 3469·2019-08-30 12:57
阅读 1960·2019-08-29 15:30
阅读 1924·2019-08-29 13:19