Open WangShuXian6 opened 3 years ago
代码不只是计算机的一组指令
代码的更重要的作用是作为与其他人交流的一种手段。
花在“编码”上的大量时间实际上是花在阅读现有代码上。 很少有人享有这样的特权:把全部或大部分时间都花在简单地敲出所有新代码上,从不处理别人(或我们过去的自己)写的代码上。
据广泛估计,开发人员将70%的代码维护时间花在阅读上以理解它。 程序员每天编写的代码行数的平均值大约是10行。我们每天花7个小时来阅读代码,去理解这10行怎么运行!
例如,一旦您学习了“map(…)”的功能,当您在任何程序中看到它时,您几乎可以立即发现并理解它。
但是每次你看到一个“for”循环,你就必须阅读整个循环才能理解它。
“for”循环的语法可能是熟悉的,但实际上它所做的并不是;你每次都必须读才能理解。
通过拥有看一眼就能识别的代码的能力,从而减少时间去了解代码在做什么,我们的注意力被释放出来,去思考更高层次的程序逻辑;那些都是最需要我们关注的重要内容。
命令式代码描述我们大多数人已经自然编写的代码;它专注于精确指导计算机“如何”做某事。
声明式代码遵循函数式编程原则是更专注于描述结果输出的代码。
函数式编程是一种非常不同的方式来考虑代码应该如何构造,从而使数据流更加明显
命令式的,几乎完全集中于“如何”完成任务;它充斥着“if”语句、“for”循环、临时变量、重新分配、值突变、带有副作用的函数调用以及函数之间的隐式数据流。 当然,你“可以”通过它的逻辑来查看数字是如何流动和更改到最终状态的,但它一点也不清楚或直接。
var numbers = [4,10,0,27,42,17,15,-6,58]; var faves = []; var magicNumber = 0;
pickFavoriteNumbers(); calculateMagicNumber(); outputMsg(); // The magic number is: 42
// ***
function calculateMagicNumber() { for (let fave of faves) { magicNumber = magicNumber + fave; } }
function pickFavoriteNumbers() { for (let num of numbers) { if (num >= 10 && num <= 20) { faves.push( num ); } } }
function outputMsg() {
var msg = The magic number is: ${magicNumber}
;
console.log( msg );
}
## 函数式
>更具声明性一些;它消除了前面提到的大多数命令式技术。
>注意没有显式的条件、循环、副作用、重新分配或突变;
>相反,它使用我们所说的函数式编程和可信的模式,如过滤、还原、转换和组合。
>注重从低级别的“如何”转移到高级的“结果”。
>我们没有使用“if”语句来测试一个数字,而是将其委托给一个函数式编程里的实用程序,如“gte(..)”(大于或等于)去操作,然后将重点放在更重要的任务上,即将该过滤器与另一个过滤器和求和函数组合起来,得到我们想要的结果。
>数据流是明确的:
```js
var sumOnlyFavorites = FP.compose( [
FP.filterReducer( FP.gte( 10 ) ),
FP.filterReducer( FP.lte( 20 ) )
] )( sum );
var printMagicNumber = FP.pipe( [
FP.reduce( sumOnlyFavorites, 0 ),
constructMsg,
console.log
] );
var numbers = [4,10,0,27,42,17,15,-6,58];
printMagicNumber( numbers ); // The magic number is: 42
// ***************
function sum(x,y) { return x + y; }
function constructMsg(v) { return `The magic number is: ${v}`; }
首先创建一个函数 sumOnlyFavorites(..) 这是其他三个函数的组合。 结合了两个过滤器, 一个检查值是否大于或等于10,一个检查值是否小于或等于20. 然后使用 sum(..) 减少数据传输. 结果函数 sumOnlyFavorites(..) 作为缩减作用,用于检查一个值是否通过两个过滤器,如果通过,则将该值添加到累加器值中。
然后使用定义好的函数 sumOnlyFavorites(..) 它可以首先减少一个数字列表, 然后使用另一个函数 printMagicNumber(..) 打印产生通过“sumOnlyFavorites”计算出数字的总和. 函数 printMagicNumber(..) 把最后的总数再输送到 constructMsg(..), 进入 console.log(..)打印创建一个字符串值结果.
函数式编程不仅仅是用“function”关键字进行定义来编程。
从表面上看:函数是可以执行一次或多次的代码集合。
f(x)
y=f(x)
一个方程定义:
f(x) = 2x2 + 3
对于x的任何值,比如2,如果你把它插入方程,你得到11。11是什么?它是f(x)
函数的返回值,前面我们说它代表y值。可以选择将输入和输出值解释为图中曲线上
(2,11)
处的点。 对于插入的每一个'x'值,我们得到另一个'y'值,作为一个点的坐标与它配对。 另一个是(0,3),另一个是(-1,5)。 把所有这些点放在一起,就得到了抛物线图在数学中,函数总是有输入并有输出。 在函数式编程中经常听到的一个术语是’态射‘(morphism); 两个数学结构之间保持结构的一种过程抽象方法,例如与该函数的输出对应另一函数的输入。
在代数数学中,这些输入和输出通常被解释为要绘制图形的坐标的组成部分。 然而,在我们的程序中,虽然很少被解释为图形上的可视绘制曲线,但是我们可以定义具有各种输入和输出的函数。
进行函数编程,应该尽可能多地使用函数,并尽可能避免使用过程。 所有的“函数”都应该接受输入并返回输出。
函数必须有输入
function foo(x,y) {
// ..
}
var a = 3;
foo( a, a * 2 );
`“a”和“a2”是函数“foo(…)”调用的参数, “x”和“y”是接收参数值的参数(分别为“3”和“6”(“a2”的结果))。
从ES6开始,参数可以声明默认值。 如果没有传递该参数的参数,或者传递了值“undefined”,则将替换默认的赋值表达式。
function foo(x = 3) { console.log( x ); }
foo(); // 3 foo( undefined ); // 3 foo( null ); // null foo( 0 ); // 0
>考虑有助于函数可用性的默认情况是一个很好的实践。
>然而,在读取和理解函数如何被调用的变化方面,默认参数可能会导致更复杂的问题。
>在多大程度上依赖此功能方面要谨慎。
### 计数输入
>一个参数的函数也称为“一元”函数,二个参数的函数也称为“二元”函数,n个参数或更高的函数称为“n元”函数。
>可以通过函数引用的“length”属性来确定其参数个数
```js
function foo(x,y,z) {
// ..
}
foo.length; // 3
需要注意,某些类型的参数列表可能会使函数的“length”属性与您可能期望的不同:
function foo(x,y = 2) { // .. }
function bar(x,...args) { // .. }
function baz( {a,b} ) { // .. }
foo.length; // 1 bar.length; // 1 baz.length; // 1
>可以检查“arguments”的“length”属性,以确定实际传递了多少个:
```js
function foo(x,y,z) {
console.log( arguments.length );
}
foo( 3, 4 ); // 2
从ES5(特别是严格模式)开始,“arguments”被一些人认为是不赞成使用的;许多人会避免使用它。
但是,建议“arguments.length”,仅用于可以需要传递的参数数量的情况
function foo(...args) {
// ..
}
现在,“args”将是参数的完整数组,不管它们是什么,您可以使用“args.length”来确切知道传入了多少个参数。
function foo(...args) {
console.log( args[3] );
}
var arr = [ 1, 2, 3, 4, 5 ];
foo( ...arr ); // 4
var arr = [ 2 ];
foo( 1, ...arr, 3, ...[4,5] ); // 4
…
都会使处理参数数组更加容易。“slice(…)”、“concat(…)”和“apply(…)”的日子已经变得没有用了,这些方法都需要数组参数值。
ES6 解构是一种为您希望看到的结构类型(对象、数组等)声明模式的方法,以及如何处理其各个部分的分解(分配)
数组参数解构
给传入数组中前两个值中的每一个都提供一个参数名
[ .. ]
参数列表的括号,这称为数组参数解构。 在这个例子中,解构函数告诉引擎在这个分配位置需要一个数组。 该模式表示取数组的第一个值并将其赋给名为“x”的局部参数变量,将第二个值赋给“y”,剩下的值将赋给“args”。function foo( [x,y,...args] = [] ) { // .. }
foo( [1,2,3] );
### 声明风格的重要性
>思考下我们刚才被解构的“foo(…)”,我们可以手动处理参数:
```js
function foo(params) {
var x = params[0];
var y = params[1];
var args = params.slice( 2 );
// ..
}
声明性代码(例如,前一个“foo(…)”代码段中的解构函数,或“…”运算符用法)将重点放在代码的结果上。
命令式代码(如后一段中的手动处理的函数)更关注如何获得结果。如果你以后读到这样的命令式代码,你必须关注执行所有的代码,以理解期望的结果。变得在那里被“编码”,很容易被细节所模糊。
前面的“foo(…)”被认为更具可读性,因为解构的方法隐藏了不必要的参数输入的细节;读者可以自由地只关注处理这些参数。这显然是最重要的关注点,所以读者应该集中精力来最全面地理解代码。
无论我们使用的语言和库/框架的允许程度怎样,只要可能,我们都应该努力实现声明性、自解释的代码。
正如我们可以解构数组参数一样,我们也可以解构对象参数:
function foo( {x,y} = {} ) { console.log( x, y ); }
foo( { y: 3 } ); // undefined 3
>将一个对象作为单个参数传入,它被分解为两个单独的参数变量“x”和“y”,它们从传入的对象中分配相应属性名的值。
>“x”属性不在对象上并不重要;它只是像您所期望的那样,以一个带有“undefined”的变量结束。
>使用对象析构函数传递潜在多个参数的方法对函数式编程的好处是,只接受一个参数的函数更容易与另一个函数的单个输出组合。
#### 无序参数
>命名参数由于被指定为对象属性,所以没有从根本上进行排序。这意味着我们可以按照我们想要的任何顺序输入:
>只是简单地省略了'x'参数。当然我们也可以指定一个'x'参数,可以放在'y'的后面
>从可读性的角度来看,命名参数更灵活,更具吸引力,尤其是当相关函数可以接受三个、四个或更多输入时。
### 函数的输出
>在JavaScript中,函数总是返回一个值。
>这三个函数都具有相同的“返回”行为:
>如果没有“return”,或者只有空的“return;”,则“undefined”值是隐式的“return”。
```js
function foo() {}
function bar() {
return;
}
function baz() {
return undefined;
}
它们应该显式地“返回”一个值,而不是返回隐式的返回“undefined”。
“return”语句只能返回单个值。 因此,如果函数需要返回多个值,唯一可行的选择是将它们收集到数组或对象这样的复合值中:
function foo() {
var retValue1 = 11;
var retValue2 = 31;
return [ retValue1, retValue2 ];
}
然后,我们从“foo()”返回的两个对应项分配给“x”和“y”:
var [ x, y ] = foo(); console.log( x + y ); // 42
将多个值收集到数组(或对象)中以返回,然后将这些值解构回不同的赋值,是透明地表示函数的多个输出的一种方法。
“return”语句不仅返回函数的值。 它也是一个流控制结构;它在该点结束了函数的执行。 因此,具有多个“return”语句的函数具有多个可能的退出点,这意味着如果有多条路径可以生成该输出,则可能难以读取函数以了解其输出行为。
函数可以接收和返回任何类型的值
接收或返回一个或多个其他函数值的函数
function forEach(list,fn) { for (let v of list) { fn( v ); } }
forEach( [1,2,3,4,5], function each(val){ console.log( val ); } ); // 1 2 3 4 5
#### 作用域的保持
>一个函数在另一个函数的作用域中时的行为。
##### 闭包
>当内部函数引用外部函数中的变量时,这称为闭包。
>闭包是指当一个函数从它自己的作用域之外记住和访问变量时,即使这个函数是在另一个作用域中执行的。
## 语法
### 命名
>函数有一个“name”属性,它保存函数语法上给定的名称的字符串值
>名称标签有助于代码更易于阅读。
>堆栈跟踪调试、可靠的自引用和可读性
#### 立即调用的函数表达式(IIFES)
```js
(function IIFE(){
// 你已经知道我是立即调用函数
})();
people.map( function getPreferredName(person){
return person.nicknames[0] || person.firstName;
} );
// vs.
people.map( person => person.nicknames[0] || person.firstName );
建议支持' => '的论据是,通过使用更轻量级的语法,我们减少了函数之间的视觉边界,这使得我们可以使用简单的函数表达式
var Auth = {
authorize(ctx) {
var credentials = `${ctx.username}:${ctx.password}`;
Auth.send( credentials, function onResp(resp){
if (resp.error) ctx.displayError( resp.error );
else ctx.displaySuccess();
} );
}
};
JavaScript 函数式编程(FP)
函数式编程有许多不同的定义。