การเขียนโปรแกรมเชิงฟังก์ชันใน JavaScript อธิบายเป็นภาษาอังกฤษล้วน

สิ่งที่ยากที่สุดอย่างหนึ่งที่คุณต้องทำในการเขียนโปรแกรมคือความซับซ้อนในการควบคุม หากไม่ได้รับการพิจารณาอย่างรอบคอบขนาดและความซับซ้อนของโปรแกรมสามารถเติบโตจนถึงจุดที่สร้างความสับสนให้กับผู้สร้างโปรแกรมได้

ในความเป็นจริงดังที่ผู้เขียนคนหนึ่งกล่าวไว้:

"ศิลปะการเขียนโปรแกรมคือทักษะในการควบคุมความซับซ้อน" - Marijn Haverbeke

ในบทความนี้เราจะอธิบายแนวคิดการเขียนโปรแกรมที่สำคัญ แนวคิดการเขียนโปรแกรมนี้สามารถช่วยให้คุณควบคุมความซับซ้อนและเขียนโปรแกรมได้ดีขึ้น

ในตอนท้ายของบทความนี้คุณจะรู้ว่าการเขียนโปรแกรมเชิงฟังก์ชันคืออะไรประเภทของฟังก์ชันที่มีหลักการของการเขียนโปรแกรมเชิงฟังก์ชันและมีความเข้าใจอย่างลึกซึ้งเกี่ยวกับฟังก์ชันลำดับที่สูงขึ้น

ฉันคิดว่าคุณมีความรู้พื้นฐานของฟังก์ชันอยู่แล้ว แนวคิดพื้นฐานของฟังก์ชันจะไม่กล่าวถึงในบทความนี้

หากคุณต้องการตรวจสอบฟังก์ชันใน JavaScript อย่างรวดเร็วฉันได้เขียนบทความโดยละเอียดที่นี่

Functional Programming คืออะไร?

การเขียนโปรแกรมเชิงฟังก์ชันเป็นกระบวนทัศน์การเขียนโปรแกรมหรือรูปแบบของการเขียนโปรแกรมที่อาศัยการใช้ฟังก์ชันที่บริสุทธิ์และแยกเป็นส่วนใหญ่

เช่นเดียวกับที่คุณอาจเดาได้จากชื่อการใช้ฟังก์ชันเป็นองค์ประกอบหลักของการเขียนโปรแกรมเชิงฟังก์ชัน แต่การใช้ฟังก์ชันเพียงอย่างเดียวไม่ได้แปลเป็นการเขียนโปรแกรมเชิงฟังก์ชัน

ในการเขียนโปรแกรมเชิงฟังก์ชันเราใช้ฟังก์ชันบริสุทธิ์ซึ่งเป็นฟังก์ชันที่ไม่มีผลข้างเคียง ฉันจะอธิบายว่าทั้งหมดนี้หมายถึงอะไร

ก่อนที่จะเจาะลึกลงไปในบทความให้เราทำความเข้าใจกับคำศัพท์และประเภทของฟังก์ชันที่มีอยู่

ประเภทของฟังก์ชัน

มีสี่ประเภทหลักของฟังก์ชัน

ฟังก์ชั่นชั้นหนึ่ง

ใน JavaScript ฟังก์ชันทั้งหมดเป็นฟังก์ชันชั้นหนึ่ง นั่นหมายความว่าสามารถปฏิบัติได้เหมือนตัวแปรอื่น ๆ

ฟังก์ชันชั้นหนึ่งเป็นฟังก์ชันที่สามารถกำหนดเป็นค่าให้กับตัวแปรส่งคืนจากฟังก์ชันอื่นและส่งผ่านเป็นอาร์กิวเมนต์ไปยังฟังก์ชันอื่น ๆ

ลองพิจารณาตัวอย่างของฟังก์ชันที่ส่งไปยังตัวแปร:

const helloWorld = () => { console.log("Hello, World"); // Hello, World }; helloWorld(); 

ฟังก์ชันการโทรกลับ

ฟังก์ชันเรียกกลับคือฟังก์ชันที่ส่งผ่านไปยังฟังก์ชันอื่น ๆ เป็นอาร์กิวเมนต์และถูกเรียกใช้โดยฟังก์ชันที่ส่งผ่านไป

ฟังก์ชันเรียกกลับเป็นฟังก์ชันที่เราเขียนเป็นอาร์กิวเมนต์ในฟังก์ชันอื่น ๆ เราไม่สามารถเรียกใช้ฟังก์ชันเรียกกลับได้ พวกเขาจะถูกเรียกใช้เมื่อฟังก์ชันหลักที่ส่งผ่านเป็นอาร์กิวเมนต์ถูกเรียกใช้

ลองดูตัวอย่าง:

const testValue = (value, test) => { if (test(value)) { return `${value} passed the test`; } else return `${value} did not pass the test`; }; const checkString = testValue('Twitter', string => typeof string === 'string'); checkString; // Twitter passed the test 

testValueเป็นฟังก์ชันที่รับค่าและฟังก์ชันเรียกกลับtest  ซึ่งส่งคืน "ค่าผ่านการทดสอบ" หากค่าส่งกลับเป็นจริงเมื่อส่งผ่านไปยังฟังก์ชันเรียกกลับ

ในกรณีนี้ฟังก์ชันเรียกกลับเป็นอาร์กิวเมนต์ที่สองที่เราส่งผ่านเข้าไปในtestValueฟังก์ชัน เรียกใช้เมื่อtestValueฟังก์ชันถูกเรียกใช้

ฟังก์ชั่นการสั่งซื้อที่สูงขึ้น

ฟังก์ชันลำดับที่สูงกว่าคือฟังก์ชันที่รับฟังก์ชันอื่นเป็นอาร์กิวเมนต์หรือส่งคืนฟังก์ชัน

ในบทความนี้เราจะอธิบายรายละเอียดเพิ่มเติมเกี่ยวกับฟังก์ชันลำดับที่สูงขึ้นและเหตุใดจึงเป็นข้อกำหนดที่ทรงพลัง สำหรับตอนนี้สิ่งที่คุณต้องรู้คือฟังก์ชันประเภทนี้รับฟังก์ชันอื่นเป็นอาร์กิวเมนต์หรือฟังก์ชันส่งคืน

ฟังก์ชันอะซิงโครนัส

ฟังก์ชันอะซิงโครนัสคือฟังก์ชันที่ไม่มีชื่อและไม่สามารถใช้ซ้ำได้ โดยปกติแล้วฟังก์ชันเหล่านี้จะถูกเขียนขึ้นเมื่อเราต้องการดำเนินการบางอย่างเพียงครั้งเดียวและในที่เดียว

ตัวอย่างที่สมบูรณ์แบบของฟังก์ชันอะซิงโครนัสคือสิ่งที่เราเขียนไว้ก่อนหน้านี้ในบทความ

const checkString = testValue('Twitter', value => typeof value === 'string'); checkString; // Refer to previous code snippet

checkStringคือตัวแปรที่มีค่าเป็นฟังก์ชัน เราส่งสองอาร์กิวเมนต์ในฟังก์ชันนี้

'Twitter'เป็นอาร์กิวเมนต์แรกและตัวที่สองเป็นฟังก์ชันอะซิงโครนัส ฟังก์ชันนี้ไม่มีชื่อเดียวและมีเพียงงานเดียว: เพื่อตรวจสอบว่าค่าที่กำหนดเป็นสตริงหรือไม่

หลักการ Meme

หลักการเขียนโปรแกรมฟังก์ชั่น

ก่อนหน้านี้ในบทความฉันพูดพาดพิงถึงข้อเท็จจริงที่ว่าการใช้ฟังก์ชันเพียงอย่างเดียวไม่ได้แปลเป็นการเขียนโปรแกรมเชิงฟังก์ชัน

มีหลักการบางอย่างที่เราต้องเข้าใจว่าโปรแกรมของเรามีคุณสมบัติตรงตามมาตรฐานการเขียนโปรแกรมเชิงฟังก์ชันหรือไม่ ลองดูที่

หลีกเลี่ยงการกลายพันธุ์และผลข้างเคียง

หลักการแรกของการเขียนโปรแกรมเชิงฟังก์ชันคือหลีกเลี่ยงการเปลี่ยนแปลงสิ่งต่างๆ ฟังก์ชันไม่ควรเปลี่ยนแปลงอะไรเช่นตัวแปรส่วนกลาง

สิ่งนี้สำคัญมากเนื่องจากการเปลี่ยนแปลงมักนำไปสู่จุดบกพร่อง ตัวอย่างเช่นหากฟังก์ชันเปลี่ยนตัวแปรส่วนกลางอาจทำให้เกิดพฤติกรรมที่ไม่คาดคิดในทุกตำแหน่งที่ใช้ตัวแปรนั้น

หลักการที่สองคือฟังก์ชันต้องบริสุทธิ์หมายความว่าไม่มีผลข้างเคียง ในการเขียนโปรแกรมเชิงฟังก์ชันการเปลี่ยนแปลงที่เกิดขึ้นเรียกว่าการกลายพันธุ์และผลลัพธ์เรียกว่าผลข้างเคียง

A pure function does neither of the two. A pure function will always have the same output for the same input.

If a function depends on a global variable, that variable should be passed to the function as an argument. This allows us to obtain the same output for the same input.

Here is an example:

const legalAgeInTheUS = 21; const checkLegalStatus = (age, legalAge) => { return age >= legalAge ? 'Of legal age.' : 'Not of legal age.'; }; const johnStatus = checkLegalStatus(18, legalAgeInTheUS); johnStatus; // Not of legal age legalAgeInTheUS; // 21 

Abstraction

Abstractions hide details and allow us to talk about problems at a higher level without describing all the implementation details of the problem.

We use abstractions in all almost all aspects of our lives, especially in speech.

For example, instead of saying "I'm going to exchange money for a machine that once plugged in displays moving images accompanied with sound", you are most likely to say "I'm going to buy a television".

In this case buy and television are abstractions. These forms of abstractions make speech a lot more easier and reduce the chances of saying the wrong thing.

But you'll agree with me that before using abstract terms like buy you need to first understand the meaning of the term and the problem it abstracts.

Functions allow us to achieve something similar. We can create functions for tasks that we are most likely to repeat again and again. Functions allows us to create our own abstractions.

On top of creating our own abstractions, some functions have already been created for us to abstract tasks that we are most likely to do time and again.

So we are going to look at some of these higher order functions that already exist to abstract repetitive tasks.

Filtering Arrays

When working with data structures like arrays, we are most likely to find ourselves in a situation where we are only interested in certain items in the array.

To obtain these items we can easily create a function to do the task:

function filterArray(array, test) { const filteredArray = []; for (let item of array) { if (test(item)) { filteredArray.push(item); } } return filteredArray; }; const mixedArray = [1, true, null, "Hello", undefined, "World", false]; const onlyStrings = filterArray(mixedArray, item => typeof item === 'string'); onlyStrings; // ['Hello', 'World'] 

filterArray is a function that accepts an array and a callback function. It loops through the array and adds the items that pass the test in the callback function into an array called filteredArray.

Using this function we are able to filter an array and return items that we're interested in, such as in the case of mixedArray.

Imagine if we had 10 different programs and in each program we needed to filter an array. Sooner or later it would become extremely tiresome to rewrite the same function over and over again.

Luckily someone already thought about this. Arrays have a standard filter method. It returns a new array with the items in the array it receives that pass the test that we provide.

const mixedArray = [1, true, null, "Hello", undefined, "World", false]; const stringArray = mixedArray.filter(item => typeof item === 'string') stringArray; // ['Hello', 'World'] 

Using the standard filter method we were able to achieve the same results we did when we defined our own function in the previous example. So, the filter method is an abstraction of the first function we wrote.

Transforming Array Items With Map

Imagine another scenario where we have an array of items but we would like to perform a certain operation on all the items. We can write a function to do this for us:

function transformArray(array, test) { const transformedArray = []; for (let item of array) { transformedArray.push(test(item)); } return transformedArray; }; const ages = [12, 15, 21, 19, 32]; const doubleAges = transformArray(ages, age => age * 2); doubleAges; // [24, 30, 42, 38, 64]; 

Just like that we  have created a function that loops through any given array and transforms all the items in the array based on the callback function the we provide.

But again this would grow tedious if we had to rewrite the function in 20 different programs.

Again, someone thought about this for us, and luckily arrays have a standard method called map which does the same exact thing. It applies the callback function on all the items in the given array and then it returns a new array.

const ages = [12, 15, 21, 19, 32]; const doubleAges = ages.map(age => age * 2); doubleAges; // [24, 30, 42, 38, 64]; 

Reducing Arrays with Reduce

Here's another scenario: You have an array of numbers, but you would like to compute the sum of all these numbers and return it. Of course you can write a function to do this for you.

function reduceArray(array, test, start) { let sum = start; for (let item of array) { sum = test(sum, item) } return sum; } let numbers = [5, 10, 20]; let doubleNumbers = reduceArray(numbers, (a, b) => a + b, 0); doubleNumbers; // 35 

Similar to the previous examples we just looked at, arrays have a standard reduce method that has the same logic as the function we just wrote above.

The reduce method is used to reduce an array to a single value based on the callback function that we provide. It also takes an optional second argument which specifies where we want the operation in the callback to start from.

The callback function we provide in the reduce function has two parameters. The first parameter is the first item in the array by default. Otherwise it is the second argument we provide into the reduce method. The second parameter is the current item in the array.

let numbers = [5, 10, 20]; let doubleNumbers = numbers.reduce((a, b) => a + b, 10); doubleNumbers; // 45 //The above example uses the reduce method to add all the items in the array starting from 10.

Other Useful Array Methods

Array.some()

All arrays have the some method which accepts a callback function. It returns true if any element in the array passes the test given in the callback  function. Otherwise it returns false:

const numbers = [12, 34, 75, 23, 16, 63] console.log(numbers.some(item => item < 100)) // true

Array.every()

The every method is the opposite of the some method. It also accepts a callback function and returns true if all the items in the array pass the test given in the callback  function. Otherwise it returns false:

const numbers = [12, 34, 75, 23, 16, 63] console.log(numbers.every(item => item < 100)) // true

Array.concat()

The concat method, short for concatenate, is a standard array method that concatenates or joins two arrays and returns a new array:

const array1 = ['one', 'two', 'three']; const array2 = ['four', 'five', 'six']; const array3 = array1.concat(array2); array3; // [ 'one', 'two', 'three', 'four', 'five', 'six' ]

Array.slice()

The slice method is an array method which copies the items of an array from a given index and returns a new array with the copied items. The slice method accepts two arguments.

อาร์กิวเมนต์แรกรับดัชนีที่จะเริ่มคัดลอก อาร์กิวเมนต์ที่สองได้รับดัชนีที่จะหยุดการคัดลอก ส่งคืนอาร์เรย์ใหม่พร้อมรายการที่คัดลอกจากดัชนีเริ่มต้น (เอกสิทธิ์) ไปยังดัชนีสุดท้าย (รวม)

อย่างไรก็ตามโปรดทราบว่าวิธี slice ไม่ใช้การสร้างดัชนีเป็นศูนย์ ดังนั้นดัชนีของรายการอาร์เรย์แรกคือ 1 ไม่ใช่ 0:

const numbers = [1,2,3,4,5,7,8]; console.log(theArray.slice(1, 4)); // [ 2, 3, 4 ] 

สรุป

ฉันหวังว่าคุณจะสนุกกับการอ่านบทความนี้และเรียนรู้สิ่งใหม่ ๆ ในเวลาเดียวกัน

มีวิธีอาร์เรย์และสตริงมากมายที่ฉันไม่ได้กล่าวถึงในบทความนี้ หากต้องการใช้เวลาหาข้อมูลเกี่ยวกับวิธีการเหล่านั้น

หากคุณต้องการที่จะเชื่อมต่อกับฉันหรือแค่ทักทาย? อย่าลังเลที่จะทำผ่าน Twitter ฉันยังแบ่งปันเคล็ดลับและแหล่งข้อมูลที่น่าสนใจสำหรับนักพัฒนา เหรอ?