Artikel ini akan memperkenalkan anda kepada pengaturcaraan berfungsi dalam JavaScript, memperkenalkan fungsi tertib tinggi, fungsi kari dan gabungan, serta fungsi fungsi biasa saya harap ia akan membantu anda!
Pengaturcaraan berorientasikan objek dan Pengaturcaraan fungsional ialah dua paradigma pengaturcaraan yang sangat berbeza yang mempunyai peraturan dan kekurangan kelebihannya sendiri.
Walau bagaimanapun, JavaScript tidak selalu mengikut satu peraturan, tetapi berada tepat di tengah-tengah dua peraturan ini Ia menyediakan beberapa aspek bahasa OOP biasa, seperti kelas, objek, warisan, dsb. Tetapi pada masa yang sama, ia juga memberi anda beberapa konsep pengaturcaraan berfungsi, seperti fungsi tertib lebih tinggi dan keupayaan untuk mengarangnya.
Kami bermula dengan yang paling penting daripada tiga konsep: fungsi tertib tinggi.
Fungsi tertib tinggi bermakna fungsi bukan hanya satu yang boleh ditakrifkan dan dipanggil daripada kod, anda sebenarnya boleh menggunakannya sebagai entiti yang boleh diperuntukkan. Jika anda telah menggunakan beberapa JavaScript, ini tidak menghairankan. Menetapkan fungsi tanpa nama kepada pemalar adalah sangat biasa.
const adder = (a, b) => { return a + b }
Logik di atas tidak sah dalam banyak bahasa lain, dapat menetapkan fungsi seperti integer adalah alat yang sangat berguna, malah kebanyakan topik yang diliputi dalam artikel ini adalah hasil sampingan daripada fungsi ini .
Faedah fungsi tertib tinggi: tingkah laku terkapsul
Dengan fungsi tertib tinggi, kita bukan sahaja boleh memperuntukkan fungsi seperti di atas, tetapi juga menggunakannya sebagai lulus Parameter. Ini membuka pintu untuk mencipta asas kod yang sentiasa dinamik di mana tingkah laku kompleks boleh digunakan semula dengan menghantarnya terus sebagai parameter.
Bayangkan bekerja dalam persekitaran berorientasikan objek tulen dan anda ingin melanjutkan kefungsian kelas untuk menyelesaikan tugas. Dalam kes ini, anda mungkin menggunakan warisan dengan merangkum logik pelaksanaan itu dalam kelas abstrak dan kemudian memanjangkannya ke dalam satu set kelas pelaksanaan. Ini adalah tingkah laku OOP yang sempurna dan ia berfungsi, kami:
Sekarang, apa yang kita mahu ialah menggunakan semula logik, kita hanya boleh mengekstrak logik boleh guna semula ke dalam fungsi dan kemudian Fungsi ini dihantar sebagai parameter kepada mana-mana fungsi lain dengan cara ini, kita boleh menyimpan beberapa proses "boilerplate", kerana kita hanya mencipta fungsi.
Kod di bawah menunjukkan cara menggunakan semula logik program dalam OOP.
//Encapsulated behavior封装行为stract class LogFormatter { format(msg) { return Date.now() + "::" + msg } } //重用行为 class ConsoleLogger extends LogFormatter { log(msg) { console.log(this.format(msg)) } } class FileLogger extends LogFormatter { log(msg) { writeToFileSync(this.logFile, this.format(msg)) } }
Perwakilan kedua ialah mengekstrak logik ke dalam fungsi di mana kita boleh mencampur dan memadankan untuk mencipta apa yang kita perlukan dengan mudah. Anda boleh terus menambah lebih banyak ciri pemformatan dan penulisan dan kemudian hanya campurkannya dengan satu baris kod:
// 泛型行为抽象 function format(msg) { return Date.now() + "::" + msg } function consoleWriter(msg) { console.log(msg) } function fileWriter(msg) { let logFile = "logfile.log" writeToFileSync(logFile, msg) } function logger(output, format) { return msg => { output(format(msg)) } } // 通过组合函数来使用它 const consoleLogger = logger(consoleWriter, format) const fileLogger = logger(fileWriter, format)
Kedua-dua kaedah mempunyai kelebihan dan kedua-duanya sangat berkesan, tiada siapa yang terbaik. Hanya untuk menunjukkan fleksibiliti pendekatan ini, kami mempunyai keupayaan untuk menghantar gelagat (iaitu fungsi) sebagai hujah seolah-olah ia adalah jenis primitif (seperti integer atau rentetan).
Faedah fungsi tertib tinggi: kod ringkas
Contoh yang baik bagi faedah ini ialah kaedah Array
, seperti forEach
, map
, reduce
Tunggu. Dalam bahasa pengaturcaraan tidak berfungsi seperti C, melelaran elemen tatasusunan dan mengubahnya memerlukan penggunaan gelung for
atau beberapa binaan gelung lain. Ini memerlukan kita menulis kod dengan cara yang ditentukan, iaitu keperluan menerangkan proses di mana kitaran berlaku.
let myArray = [1,2,3,4] let transformedArray = [] for(let i = 0; i < myArray.length; i++) { transformedArray.push(myArray[i] * 2) }
Kod di atas terutamanya:
i
, yang akan digunakan sebagai indeks myArray
dan julat nilainya ialah < Panjang 🎜> hingga 0
myArray
i
pada kedudukan myArray
dan tambahkannya pada tatasusunan i
. transformedArray
const double = x => x * 2; let myArray = [1,2,3,4]; let transformedArray = myArray.map(double);
dan map
) jadi anda tidak perlu risau tentang memahami cara ia berfungsi. Anda juga boleh menyembunyikan logik pendaraban di dalam fungsi dalam contoh pertama, tetapi logik traversal mesti ada, yang menambah beberapa halangan bacaan yang tidak perlu. double
Fungsi kari ialah untuk mengubah fungsi yang menerima berbilang parameter menjadi satu yang menerima satu parameter (parameter pertama bagi yang asal function) Fungsi yang mengambil satu parameter) dan mengembalikan fungsi baharu yang menerima parameter yang tinggal dan mengembalikan hasil. Mari lihat contoh:
function adder(a, b) { return a + b } // 变成 const add10 = x => adder(a, 10)
pada julat nilai, anda boleh memanggil 10
dan bukannya menggunakan nilai kedua yang sama setiap kali Panggilan add10
dengan parameter. Contoh ini mungkin kelihatan bodoh, tetapi ia merangkumi cita-cita adder
. 柯里化
你可以将柯里化视为函数式编程的继承,然后按照这种思路再回到logger
的示例,可以得到以下内容:
function log(msg, msgPrefix, output) { output(msgPrefix + msg) } function consoleOutput(msg) { console.log(msg) } function fileOutput(msg) { let filename = "mylogs.log" writeFileSync(msg, filename) } const logger = msg => log(msg, ">>", consoleOutput); const fileLogger = msg => log(msg, "::", fileOutput);
log
的函数需要三个参数,而我们将其引入仅需要一个参数的专用版本中,因为其他两个参数已由我们选择。
注意,这里将log
函数视为抽象类,只是因为在我的示例中,不想直接使用它,但是这样做是没有限制的,因为这只是一个普通的函数。 如果我们使用的是类,则将无法直接实例化它。
函数组合就是组合两到多个函数来生成一个新函数的过程。将函数组合在一起,就像将一连串管道扣合在一起,让数据流过一样。
在计算机科学中,函数组合是将简单函数组合成更复杂函数的一种行为或机制。就像数学中通常的函数组成一样,每个函数的结果作为下一个函数的参数传递,而最后一个函数的结果是整个函数的结果。
这是来自维基百科的函数组合的定义,粗体部分是比较关键的部分。使用柯里化时,就没有该限制,我们可以轻松使用预设的函数参数。
代码重用听起来很棒,但是实现起来很难。如果代码业务性过于具体,就很难重用它。如时代码太过通用简单,又很少人使用。所以我们需要平衡两者,一种制作更小的、可重用的部件的方法,我们可以将其作为构建块来构建更复杂的功能。
在函数式编程中,函数是我们的构建块。每个函数都有各自的功能,然后我们把需要的功能(函数)组合起来完成我们的需求,这种方式有点像乐高的积木,在编程中我们称为 组合函数。
看下以下两个函数:
var add10 = function(value) { return value + 10; }; var mult5 = function(value) { return value * 5; };
上面写法有点冗长了,我们用箭头函数改写一下:
var add10 = value => value + 10; var mult5 = value => value * 5;
现在我们需要有个函数将传入的参数先加上 10 ,然后在乘以 5, 如下:
现在我们需要有个函数将传入的参数先加上 10 ,然后在乘以 5, 如下:
var mult5AfterAdd10 = value => 5 * (value + 10)
尽管这是一个非常简单的例子,但仍然不想从头编写这个函数。首先,这里可能会犯一个错误,比如忘记括号。第二,我们已经有了一个加 10 的函数 add10 和一个乘以 5 的函数 mult5 ,所以这里我们就在写已经重复的代码了。
使用函数 add10,mult5 来重构 mult5AfterAdd10 :
var mult5AfterAdd10 = value => mult5(add10(value));
我们只是使用现有的函数来创建 mult5AfterAdd10,但是还有更好的方法。
在数学中, f ∘ g 是函数组合,叫作“f 由 g 组合”,或者更常见的是 “f after g”。 因此 (f ∘ g)(x) 等效于f(g(x)) 表示调用 g 之后调用 f。
在我们的例子中,我们有 mult5 ∘ add10 或 “add10 after mult5”,因此我们的函数的名称叫做 mult5AfterAdd10。由于Javascript本身不做函数组合,看看 Elm 是怎么写的:
add10 value = value + 10 mult5 value = value * 5 mult5AfterAdd10 value = (mult5 << add10) value
在 Elm 中 << 表示使用组合函数,在上例中 value 传给函数 add10 然后将其结果传递给 mult5。还可以这样组合任意多个函数:
f x = (g << h << s << r << t) x
这里 x 传递给函数 t,函数 t 的结果传递给 r,函数 t 的结果传递给 s,以此类推。在Javascript中做类似的事情,它看起来会像 g(h(s(r(t(x))))),一个括号噩梦。
函数式语言中3个常见的函数:Map,Filter,Reduce。
如下JavaScript代码:
for (var i = 0; i < something.length; ++i) { // do stuff }
这段代码存在一个很大的问题,但不是bug。问题在于它有很多重复代码(boilerplate code)。如果你用命令式语言来编程,比如Java,C#,JavaScript,PHP,Python等等,你会发现这样的代码你写地最多。这就是问题所在。
现在让我们一步一步的解决问题,最后封装成一个看不见 for 语法函数:
先用名为 things 的数组来修改上述代码:
var things = [1, 2, 3, 4]; for (var i = 0; i < things.length; ++i) { things[i] = things[i] * 10; // 警告:值被改变! } console.log(things); // [10, 20, 30, 40]
这样做法很不对,数值被改变了!
在重新修改一次:
var things = [1, 2, 3, 4]; var newThings = []; for (var i = 0; i < things.length; ++i) { newThings[i] = things[i] * 10; } console.log(newThings); // [10, 20, 30, 40]
这里没有修改things数值,但却却修改了newThings。暂时先不管这个,毕竟我们现在用的是 JavaScript。一旦使用函数式语言,任何东西都是不可变的。
现在将代码封装成一个函数,我们将其命名为 map,因为这个函数的功能就是将一个数组的每个值映射(map)到新数组的一个新值。
var map = (f, array) => { var newArray = []; for (var i = 0; i < array.length; ++i) { newArray[i] = f(array[i]); } return newArray; };
函数 f 作为参数传入,那么函数 map 可以对 array 数组的每项进行任意的操作。
现在使用 map 重写之前的代码:
var things = [1, 2, 3, 4]; var newThings = map(v => v * 10, things);
这里没有 for 循环!而且代码更具可读性,也更易分析。
现在让我们写另一个常见的函数来过滤数组中的元素:
var filter = (pred, array) => { var newArray = []; for (var i = 0; i < array.length; ++i) { if (pred(array[i])) newArray[newArray.length] = array[i]; } return newArray; };
当某些项需要被保留的时候,断言函数 pred 返回TRUE,否则返回FALSE。
使用过滤器过滤奇数:
var isOdd = x => x % 2 !== 0; var numbers = [1, 2, 3, 4, 5]; var oddNumbers = filter(isOdd, numbers); console.log(oddNumbers); // [1, 3, 5]
比起用 for 循环的手动编程,filter 函数简单多了。最后一个常见函数叫reduce。通常这个函数用来将一个数列归约(reduce)成一个数值,但事实上它能做很多事情。
在函数式语言中,这个函数称为 fold。
var reduce = (f, start, array) => { var acc = start; for (var i = 0; i < array.length; ++i) acc = f(array[i], acc); // f() 有2个参数 return acc; });
reduce函数接受一个归约函数 f,一个初始值 start,以及一个数组 array。
这三个函数,map,filter,reduce能让我们绕过for循环这种重复的方式,对数组做一些常见的操作。但在函数式语言中只有递归没有循环,这三个函数就更有用了。附带提一句,在函数式语言中,递归函数不仅非常有用,还必不可少。
英文原文地址:https://blog.bitsrc.io/functional-programming-in-functions-composition-and-currying-3c765a50152e
作者:Fernando Doglio
更多编程相关知识,请访问:编程视频!!
Atas ialah kandungan terperinci Analisis mendalam tentang fungsi peringkat tinggi, fungsi kari dan gabungan dalam JavaScript. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!