"掌握 JavaScript 面试" 是旨在准备候选人在申请中高级别职位时可能遇到的常见问题的系列文章之一。这些问题也是我经常在真实面试中使用到的问题。
函数式编程在 JavaScript 世界里已经成为一个很火的话题。仅仅几年前,只有很少的 JavaScript 开发者知道函数式编程。但是过去三年我见过的各个大项目的基础代码都深入使用了函数式编程的想法。
函数式编程式是由纯函数、局部声明、变量所组成的构建过程。函数式编程是声明式而不是命令式,应用程序状态流经整个函数。不同于面向对象编程的应用状态在对象和成员函数里共享。
函数式编程是一种基于一些基本的、明确的规则构建软件的编程规则。其他一些编程规则包括面向对象和面向过程式编程。
函数式代码往往比命令式或面向对象的代码更简洁、更可预测、更容易测试 - 但是如果你不熟悉它和与之相关的常见模式,函数式代码也可以看起来更加密集、而且相关文献对于新手来说是更不可读。
如果你开始使用 Google 函数式编程的术语,那么你将很快遇到学术语言的一堵砖墙,这对初学者来说可能非常吓人。简单的说它有一个学习曲线是对其严重的低估。但是,如果你已经在 JavaScript 中编程了一段时间,那么这是很好的机会在实际的软件中使用很多函数式编程概念和实用程序。
不要被新词吓走。它比听起来简单得多。
最难的部分是包装你的 head 部分,那里面到处都是不熟悉的词汇。在你开始掌握函数式编程的意义之前,需要理解所有看起来很不清楚的定义,而且这些不清楚的定义还挺多的:
总而言之,如果你想要知道函数式编程的意义就要多实践,你必须由理解那些核心概念入手。
纯函数是一个这样的函数:
纯函数具有许多在函数式编程中很重要的属性,包括 功能透明 (你可以使用其结果值替换函数调用而不改变程序的含义)。参考 "什么是纯函数" 一文以了解更多。
函数组合是组合两个或多个函数以生成一个新函数或执行一些计算的过程。例如,在 JavaScript 中组合 f . g (点表示 "由... 组成")等价于 f(g(x))。了解函数组合是了解如何使用函数式编程构建软件的重要一步。 阅读 "什么是函数组合" 一文以了解更多。
共享状态是存在于共享作用域中的任何变量,对象或内存空间,或作为对象的属性在范围之间传递。共享范围可以包括全局范围或闭包范围。通常,在面向对象编程中,通过向其他对象添加属性,让对象在范围之间共享。
例如,计算机游戏可能具有主游戏对象,其中字符和游戏选项存储为该对象所拥有的属性。函数式编程避免了共享状态 - 而是依赖于不可变数据结构和纯粹的计算,从现有数据中导出新数据。有关函数式编程如何处理应用程序状态的更多详细信息,请参阅 "10 个提示让 Redux 架构变得更好"。
共享状态的问题是为了理解函数的效果,您必须知道函数使用或影响的每个共享变量的整个历史记录。
想象一下,您有一个需要保存的用户对象。您的 saveUser()函数向服务器上的 API 发出请求。当用户使用 updateAvatar()更改其个人资料图片,并触发另一个 saveUser()请求。这种情况下保存时,服务器发回一个规范用户对象,该对象应该替换内存中的任何内容,以便与服务器上发生的更改或响应其他 API 调用同步。
不巧的是,在第一个响应之前收到第二个响应,所以当第一个(现在已经过时的)响应被返回时,新的配置文件图片将在内存中被擦除并被旧的替换。这是一个竞争条件的例子 - 一个与共享状态相关联的非常常见的错误。
与共享状态相关的另一个常见问题是,更改调用函数的顺序可能会导致级联的失败,因为作用于共享状态的函数与时序相关:
- // With shared state, the order in which function calls are made// changes the result of the function calls.const x = {
- val: 2
- };
- const x1 = () = >x.val += 1;
- const x2 = () = >x.val *= 2;
- x1();
- x2();
- console.log(x.val); // 6// This example is exactly equivalent to the above, except...const y = {
- val: 2
- };const y1 = () = >y.val += 1;const y2 = () = >y.val *= 2; // ...the order of the function calls is reversed...y2();
- y1(); // ... which changes the resulting value:console.log(y.val); // 5
当您避免共享状态时,函数调用的时序和顺序不会改变调用函数的结果。 使用纯函数,给出相同的输入,您将始终获得相同的输出。 这使得函数调用完全独立于其他函数调用,这可以从根本上简化变更和重构。 一个功能的更改或函数调用的时间不会产生波动,并破坏程序的其他部分。
- const x = {
- val: 2
- };
- const x1 = x = >Object.assign({},
- x, {
- val: x.val + 1
- });
- const x2 = x = >Object.assign({},
- x, {
- val: x.val * 2
- });
- console.log(x1(x2(x)).val); // 5const y = {
- val: 2
- }; // Since there are no dependencies on outside variables,// we don't need different functions to operate on different// variables.// this space intentionally left blank// Because the functions don't mutate, you can call these// functions as many times as you want, in any order, // without changing the result of other function calls.x2(y);
- x1(y);
- console.log(x1(x2(y)).val); // 5
在上面的例子中,我们使用 Object.assign(),并将一个空对象作为第一个参数传递,以复制 x 的属性,而不是将其替换。在这种情况下,它将相当于从零开始创建一个没有 Object.assign() 的新对象,但这是 JavaScript 中常见的模式,用于创建现有状态的副本,而不是使用突变,我们在第一个例子中演示了。
如果您仔细观察本示例中的 console.log() 语句,您应该注意到我已经提到过的一些函数组合。回想起来,函数组合如下所示:f(g(x))。在这种情况下,我们用组合的 x1() 和 x2() 替换 f() 和 g():x1. x2。
当然,如果你改变了组合的顺序,输出就会改变。操作顺序依然重要。 f(g(x)) 不总是等于 g(f(x)),但函数之外的变量会发生什么就变得不那么重要了 - 而这是一个大问题。使用不纯的函数,除非知道函数使用或影响的每个变量的整个历史记录,否则不可能完全了解函数的作用。
删除函数调用时序依赖关系,并消除整个类的潜在错误。
不可变对象是指创建后不能修改的对象。相反, 可变 对象是任何可以在创建后被修改的对象。
不变性是函数式编程的核心概念,因为没有它,程序中的数据流是有损的。状态记录被废弃了,奇怪的错误可出现在你的软件中。 更多关于不可变性的意义,请参见 "不变性之道"。
在 JavaScript 中,不要混淆 const 与不可变性。创建变量名绑定,在创建后不能重新分配。 const 创建的变量名的绑定,该变量在创建之后不能被重新赋值。const 并不能创建不可变对象。你不能更改绑定引用的对象,但你仍然可以更改对象的属性,这意味着使用 const 创建的绑定是可变的,而不是不可变的。
不可变的对象根本无法改变。 您可以通过深度冻结对象,使值真正不变。 JavaScript 有一种将一个对象深度冻结的方法:
- const a = Object.freeze({
- foo: 'Hello',
- bar: 'world',
- baz: '!'
- });
- a.foo = 'Goodbye'; // Error: Cannot assign to read only property 'foo' of object Object
但冻结的对象只是表面上不可变的。 例如,以下对象是可变的:
- const a = Object.freeze({
- foo: {
- greeting: 'Hello'
- },
- bar: 'world',
- baz: '!'
- });
- a.foo.greeting = 'Goodbye';
- console.log(`$ {
- a.foo.greeting
- },
- $ {
- a.bar
- }
- $ {
- a.baz
- }`);
如您所见,冻结对象的顶级原始属性不能更改,但是任何一个属于对象(包括数组等)的属性,仍然可以进行突变,因此,即使冻结的对象也不是一成不变的,除非您遍历整个对象树并冻结每个属性的对象。
来源: http://www.tuicool.com/articles/JreYJzz