۵۰ درصد تخفیف ویژه برای همه دوره‌ها تا پایان هفته

توابع Higher Order در جاوااسکریپت

توابع در جاوااسکریپت به عنوان اصلی‌ترین مفاهیم به‌شمار می‌آیند. می‌توانیم توابع را به ‌عنوان value در نظر بگیریم و به متغیر دیگر نسبت دهیم، آن‌ها را به‌عنوان آرگومان به تابع دیگری ارسال کنیم و یا حتی آن‌ها را از تابعی بازگردانیم. این توانایی توابع برای عمل به عنوان مفاهیم اصلی چیزی است که توابع higher order را در جاوااسکریپت تقویت می‌کند. اساساً تابعی که تابع دیگری را به عنوان آرگومان می‌گیرد یا تابعی را برمی‌گرداند به عنوان تابع higher order شناخته می‌شود.

در ادامه هر دو نوع پیاده‌سازی، یعنی ارسال یک تابع به عنوان آرگومان به تابع دیگر و بازگرداندن یک تابع از یک تابع دیگر را بررسی خواهیم کرد.

چگونه یک تابع را به عنوان آرگومان به تابع دیگری منتقل کنیم؟

در این بخش خواهیم دید که چگونه می‌توانیم یک تابع را به عنوان آرگومان ارسال کنیم و درنهایت این موضوع چگونه می‌تواند در نوشتن کدهای تمیزتر به ما کمک کند.

کد زیر را در نظر بگیرید، می‌خواهیم تابعی ایجاد کنیم که آرایه‌ای را به عنوان آرگومان بپذیرد. اعداد فرد را از آن فیلتر کند و تمام اعداد فیلتر شده را بازگرداند. به این صورت که:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
function filterOdd(arr) {
const filteredArr = [];
for (let i = 0; i < arr.length; i++) {
if (arr[i] % 2 !== 0) {
filteredArr.push(arr[i]);
}
}
return filteredArr;
}
console.log(filterOdd(arr));
// Output:
// [ ۱, ۳, ۵, ۷, ۹, ۱۱ ]
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; function filterOdd(arr) { const filteredArr = []; for (let i = 0; i < arr.length; i++) { if (arr[i] % 2 !== 0) { filteredArr.push(arr[i]); } } return filteredArr; } console.log(filterOdd(arr)); // Output: // [ ۱, ۳, ۵, ۷, ۹, ۱۱ ]
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];

function filterOdd(arr) {
  const filteredArr = [];
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] % 2 !== 0) {
      filteredArr.push(arr[i]);
    }
  }
  return filteredArr;
}
console.log(filterOdd(arr));

// Output:
// [ ۱, ۳, ۵, ۷, ۹, ۱۱ ]

تابع بالا آرایه فیلتر شده

[۱، ۳، ۵، ۷، ۹، ۱۱]
[۱، ۳، ۵، ۷، ۹، ۱۱]را بازمی‌گرداند و همانطور که انتظار داشتیم فقط اعداد فرد را شامل می‌شود.

اکنون فرض کنید می‌خواهیم تابعی بسازیم که تمام اعداد زوج را فیلتر کرده و بازگرداند. برای این کار به صورت زیر عمل می‌کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function filterEven(arr) {
const filteredArr = [];
for (let i = 0; i < arr.length; i++) {
if (arr[i] % 2 == 0) {
filteredArr.push(arr[i]);
}
}
return filteredArr;
}
console.log(filterEven(arr));
// Output:
// [ ۲, ۴, ۶, ۸, ۱۰ ]
function filterEven(arr) { const filteredArr = []; for (let i = 0; i < arr.length; i++) { if (arr[i] % 2 == 0) { filteredArr.push(arr[i]); } } return filteredArr; } console.log(filterEven(arr)); // Output: // [ ۲, ۴, ۶, ۸, ۱۰ ]
function filterEven(arr) {
  const filteredArr = [];
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] % 2 == 0) {
      filteredArr.push(arr[i]);
    }
  }
  return filteredArr;
}
console.log(filterEven(arr));

// Output:
// [ ۲, ۴, ۶, ۸, ۱۰ ]

باز هم همانطور که انتظار داشتیم خروجی دلخواه که یک آرایه با تمام اعداد زوج است، یعنی

[۲، ۴، ۶، ۸، ۱۰]
[۲، ۴، ۶، ۸، ۱۰]را دریافت می‌کنیم.

اما توجه داشته باشید که ما در این رویکرد کدهای تکراری زیادی داریم. هر دو توابع بالا کارهای مشترک زیادی مانند دریافت آرایه اصلی، ایجاد یک آرایه جدید برای ذخیره آرایه فیلتر شده، تشکیل loop روی آرایه اصلی و در نهایت بازگرداندن آرایه فیلتر شده را انجام می‌دهند.

تنها تفاوت بین هر دو تابع، منطقی است که برای فیلتر کردن آرایه اصلی استفاده می‌کنند.

برای تابع

filterOdd
filterOddاز منطق
arr[i] % 2 !== 0
arr[i] % 2 !== 0استفاده می‌کنیم در حالی که در تابع
filterEven
filterEvenبرای فیلتر کردن آرایه اصلی از منطق
arr[i] % 2 == 0
arr[i] % 2 == 0 استفاده کرده‌ایم.

اینجاست که می‌توانیم از توابع higher order بهره‌مند شویم. هدف اصلی این است که تابعی ایجاد کنیم تا تمام کارهای مشترکی که در دو تابع بالا وجود دارد را انجام دهد و سپس قسمت منطق توابع را جداگانه و به عنوان آرگومان به این تابع ارسال کنیم. برای انجام این کار مثلا می‌توانیم به شکل زیر عمل کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function filterFunction(arr, callback) {
const filteredArr = [];
for (let i = 0; i < arr.length; i++) {
callback(arr[i]) ? filteredArr.push(arr[i]) : null;
}
return filteredArr;
}
function filterFunction(arr, callback) { const filteredArr = []; for (let i = 0; i < arr.length; i++) { callback(arr[i]) ? filteredArr.push(arr[i]) : null; } return filteredArr; }
function filterFunction(arr, callback) {
  const filteredArr = [];
  for (let i = 0; i < arr.length; i++) {
    callback(arr[i]) ? filteredArr.push(arr[i]) : null;
  }
  return filteredArr;
}

فعلاً پارامتر

callback
callbackرا نادیده می‌گیریم. توجه داشته باشید که در تابع
filterFuntion
filterFuntionجدید همه مراحل مشترک در توابع
filterOdd
filterOddو
filterEven
filterEvenرا حفظ کردیم.

اکنون پارامتر

callback
callback منطق را می‌پذیرد یعنی این پارامتر خود، تابع دیگری است که حاوی منطق filtering می‌باشد. برای فیلتر کردن اعداد فرد و زوج، توابع را به شکل زیر می‌نویسیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// Function containing logic for filtering out odd numbers
function isOdd(x) {
return x % 2 != 0;
}
// Function containing logic for filtering out even numbers
function isEven(x) {
return x % 2 === 0;
}
// Function containing logic for filtering out odd numbers function isOdd(x) { return x % 2 != 0; } // Function containing logic for filtering out even numbers function isEven(x) { return x % 2 === 0; }
// Function containing logic for filtering out odd numbers

function isOdd(x) {
  return x % 2 != 0;
}

// Function containing logic for filtering out even numbers

function isEven(x) {
  return x % 2 === 0;
}

حالا باید آرایه اصلی را به همراه تابع اصلی که منطق برنامه را شامل می‌شود، به

filterFunction
filterFunctionارسال کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// For filtering out odd numbers
filterFunction(arr, isOdd)
// Output of console.log(filterFunction(arr, isOdd)):
// [ ۱, ۳, ۵, ۷, ۹, ۱۱ ]
// For filtering out even numbers
filterFunction(arr, isEven)
// Output of console.log(filterFunction(arr, isEven)):
// [ ۲, ۴, ۶, ۸, ۱۰ ]
// For filtering out odd numbers filterFunction(arr, isOdd) // Output of console.log(filterFunction(arr, isOdd)): // [ ۱, ۳, ۵, ۷, ۹, ۱۱ ] // For filtering out even numbers filterFunction(arr, isEven) // Output of console.log(filterFunction(arr, isEven)): // [ ۲, ۴, ۶, ۸, ۱۰ ]
// For filtering out odd numbers

filterFunction(arr, isOdd)
// Output of console.log(filterFunction(arr, isOdd)):
// [ ۱, ۳, ۵, ۷, ۹, ۱۱ ]

// For filtering out even numbers

filterFunction(arr, isEven)
// Output of console.log(filterFunction(arr, isEven)):
// [ ۲, ۴, ۶, ۸, ۱۰ ]

به این ترتیب ما توابع اصلی مانند

isOdd
isOddیا
isEven
isEvenرا به عنوان آرگومان به تابع
filterFunction
filterFunctionمنتقل می‌کنیم.

در این روش منطق فیلتر اصلی را از تابع اصلی جدا می‌کنیم. اکنون می‌توانیم هر منطق دیگری را همانطور که دوست داریم بدون نیاز به تغییر، به تابع

filterFunction
filterFunction ارسال کنیم.

به عنوان مثال، اگر بخواهیم اعداد بزرگ‌تر از ۵ را فیلتر کنیم، فقط باید منطق برنامه را به شکل زیر بنویسیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function isGreaterThanFive(x) {
return x > 5;
}
function isGreaterThanFive(x) { return x > 5; }
function isGreaterThanFive(x) {
  return x > 5;
}

و آن را به عنوان آرگومان به

filterFunction
filterFunction ارسال کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
filterFunction(arr, isGreaterThanFive)
// Output of console.log(filterFunction(arr, isGreaterThanFive)):
// [ ۶, ۷, ۸, ۹, ۱۰, ۱۱ ]
filterFunction(arr, isGreaterThanFive) // Output of console.log(filterFunction(arr, isGreaterThanFive)): // [ ۶, ۷, ۸, ۹, ۱۰, ۱۱ ]
filterFunction(arr, isGreaterThanFive)

// Output of console.log(filterFunction(arr, isGreaterThanFive)):
// [ ۶, ۷, ۸, ۹, ۱۰, ۱۱ ]

همچنین می‌توانیم تابع اصلی را به ‌عنوان توابع arrow نیز ارسال کنیم و همان نتیجه را بگیریم، یعنی با نوشتن

((x) => x > 5)
((x) => x > 5)به جای
isGreaterThanFive
isGreaterThanFive، همان نتیجه را دریافت می‌کنیم.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
filterFunction(arr, (x) => x > 5)
// Output of console.log(filterFunction(arr, (x) => x > 5)):
// [ ۶, ۷, ۸, ۹, ۱۰, ۱۱ ]
filterFunction(arr, (x) => x > 5) // Output of console.log(filterFunction(arr, (x) => x > 5)): // [ ۶, ۷, ۸, ۹, ۱۰, ۱۱ ]
filterFunction(arr, (x) => x > 5)

// Output of console.log(filterFunction(arr, (x) => x > 5)):
// [ ۶, ۷, ۸, ۹, ۱۰, ۱۱ ]

نحوه ایجاد Polyfills

می‌دانیم که جاوااسکریپت برخی از توابع داخلی higher order مانند

map()
map()،
filter()
filter()،
reduce()
reduce() و غیره را در اختیار ما قرار می‌دهد. اما آیا خودمان می‌توانیم این توابع را بازسازی کنیم؟

ما قبلاً تابع filtering را در بخش قبل ایجاد کردیم. اکنون یک array prototype از تابع

filterFunction
filterFunctionخود ایجاد می‌کنیم تا بتوانیم آن را با هر آرایه‌ای استفاده کنیم. به عنوان مثال:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
Array.prototype.filterFunction = function (callback) {
const filteredArr = [];
for (let i = 0; i < this.length; i++) {
callback(this[i]) ? filteredArr.push(this[i]) : null;
}
return filteredArr;
};
Array.prototype.filterFunction = function (callback) { const filteredArr = []; for (let i = 0; i < this.length; i++) { callback(this[i]) ? filteredArr.push(this[i]) : null; } return filteredArr; };
Array.prototype.filterFunction = function (callback) {
  const filteredArr = [];
  for (let i = 0; i < this.length; i++) {
    callback(this[i]) ? filteredArr.push(this[i]) : null;
  }
  return filteredArr;
};

در کد بالا،

this
thisبه آرایه‌ای اشاره دارد که prototype فراخوانی شده است. بنابراین اگر کد را به صورت زیر بنویسیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const arr = [1, 2, 3, 4, 5]
arr.filterFunction(callbackFn)
const arr = [1, 2, 3, 4, 5] arr.filterFunction(callbackFn)
const arr = [1, 2, 3, 4, 5]
arr.filterFunction(callbackFn)

در این صورت

this
thisبه آرایه
arr
arrاشاره می‌کند.

اکنون می‌توانیم از

filterFunction
filterFunction مانند تابع داخلی
filter()
filter()در جاوااسکریپت استفاده کنیم. یعنی:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
arr.filterFunction(isEven)
arr.filterFunction(isEven)
arr.filterFunction(isEven)

که شبیه فراخوانی تابع داخلی

filter()
filter() است:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
arr.filter(isEven)
arr.filter(isEven)
arr.filter(isEven)

هر دو فراخوانی تابع فوق یعنی

arr.filterFunction(isEven)
arr.filterFunction(isEven)و
arr.filter(isEven)
arr.filter(isEven)خروجی یکسانی مانند
[۲، ۴، ۶، ۸، ۱۰]
[۲، ۴، ۶، ۸، ۱۰]را به ما می‌دهند.

همچنین می‌توانیم یک تابع arrow را همانطور که می‌توانیم به تابع داخلی

filter()
filter()ارسال کنیم، به prototype نیز بفرستیم.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// I
arr.filterFunction((x) => x % 2 != 0)
arr.filter((x) => x % 2 != 0)
// both give the same output on console.log: [ 1, 3, 5, 7, 9, 11 ]
// II
arr.filterFunction((x) => x > 5)
arr.filter((x) => x > 5)
// both give the same output on console.log: [ 6, 7, 8, 9, 10, 11 ]
// I arr.filterFunction((x) => x % 2 != 0) arr.filter((x) => x % 2 != 0) // both give the same output on console.log: [ 1, 3, 5, 7, 9, 11 ] // II arr.filterFunction((x) => x > 5) arr.filter((x) => x > 5) // both give the same output on console.log: [ 6, 7, 8, 9, 10, 11 ]
// I
arr.filterFunction((x) => x % 2 != 0)
arr.filter((x) => x % 2 != 0)
// both give the same output on console.log: [ 1, 3, 5, 7, 9, 11 ]

// II
arr.filterFunction((x) => x > 5)
arr.filter((x) => x > 5)
// both give the same output on console.log: [ 6, 7, 8, 9, 10, 11 ]

به نوعی ما یک polyfill برای تابع داخلی

filter()
filter()نوشته‎‌ایم.

زنجیره توابع

می‌توانیم زنجیره‌سازی تابع را همانند تابع داخلی

filter()
filter()، با اجرای prototype پیاده‌سازی کنیم. ابتدا تمام اعداد بزرگ‌تر از ۵ را فیلتر می‌کنیم. سپس از نتیجه‌ای که بدست آوردیم، تمام اعداد زوج را فیلتر می‌کنیم. به این صورت که:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// Using our own filterFunction() prototype implementation
arr.filterFunction((x) => x > 5).filterFunction((x) => x % 2 === 0)
//Using the inbuilt filter() implementation
arr.filter((x) => x > 5).filter((x) => x % 2 === 0)
// both give the same output on console.log: [ 6, 8, 10 ]
// Using our own filterFunction() prototype implementation arr.filterFunction((x) => x > 5).filterFunction((x) => x % 2 === 0) //Using the inbuilt filter() implementation arr.filter((x) => x > 5).filter((x) => x % 2 === 0) // both give the same output on console.log: [ 6, 8, 10 ]
// Using our own filterFunction() prototype implementation
arr.filterFunction((x) => x > 5).filterFunction((x) => x % 2 === 0)

//Using the inbuilt filter() implementation
arr.filter((x) => x > 5).filter((x) => x % 2 === 0)

// both give the same output on console.log: [ 6, 8, 10 ]

به این ترتیب می‌توانیم از توابع higher order در جاوااسکریپت برای نوشتن کدهای ماژولار تمیزتر و با قابلیت نگه‌داری بیشتر استفاده کنیم.

بازگرداندن یک تابع از یک تابع دیگر در جاوااسکریپت

ما می‌توانیم یک تابع را از یک تابع دیگر بازگردانیم زیرا توابع را در جاوااسکریپت به عنوان value در نظر می‌گیریم. این مفهوم را به کمک یک مثال بررسی می‌کنیم.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function calculate(operation) {
switch (operation) {
case "ADD":
return function (a, b) {
console.log(`${a} + ${b} = ${a + b}`);
};
case "SUBTRACT":
return function (a, b) {
console.log(`${a} - ${b} = ${a - b}`);
};
}
}
function calculate(operation) { switch (operation) { case "ADD": return function (a, b) { console.log(`${a} + ${b} = ${a + b}`); }; case "SUBTRACT": return function (a, b) { console.log(`${a} - ${b} = ${a - b}`); }; } }
function calculate(operation) {
  switch (operation) {
    case "ADD":
      return function (a, b) {
        console.log(`${a} + ${b} = ${a + b}`);
      };
    case "SUBTRACT":
      return function (a, b) {
        console.log(`${a} - ${b} = ${a - b}`);
      };
  }
}

در کد بالا، وقتی تابع

calculate
calculateرا با آرگومان فراخوانی می‌کنیم، آن آرگومان را تعویض می‌کند و در نهایت یک تابع anonymous را بازمی‌گرداند. بنابراین اگر تابع
()calculate
()calculateرا فراخوانی کنیم و نتیجه آن را در یک متغیر ذخیره کنیم و در کنسول نمایش دهیم، خروجی زیر را دریافت خواهیم کرد:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const calculateAdd = calculate("ADD");
console.log(calculateAdd);
// Output:
// [Function (anonymous)]
const calculateAdd = calculate("ADD"); console.log(calculateAdd); // Output: // [Function (anonymous)]
const calculateAdd = calculate("ADD");
console.log(calculateAdd);

// Output: 
// [Function (anonymous)]

همانطور که می‌بینید متغیر

calaculateAdd
calaculateAddحاوی یک تابع anonymous است که تابع
calculate()
calculate()آن را بازگردانده است.

دو راه برای فراخوانی این تابع داخلی وجود دارد که در ادامه به بررسی آن‌ها خواهیم پرداخت.

فراخوانی تابع برگشتی با استفاده از یک متغیر

در این روش همانند مثال بالا، تابع بازگشتی را در یک متغیر ذخیره می‌کنیم و سپس آن متغیر را فراخوانی می‌کنیم تا به نوبه خود تابع داخلی را فراخوانی کند.

برای درک بهتر آن مثال زیر را باهم بررسی می‌کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const calculateAdd = calculate("ADD");
calculateAdd(2, 3);
// Output: 2 + 3 = 5
const calculateSubtract = calculate("SUBTRACT");
calculateSubtract(2, 3);
// Output: 2 - 3 = -1
const calculateAdd = calculate("ADD"); calculateAdd(2, 3); // Output: 2 + 3 = 5 const calculateSubtract = calculate("SUBTRACT"); calculateSubtract(2, 3); // Output: 2 - 3 = -1
const calculateAdd = calculate("ADD");
calculateAdd(2, 3);
// Output: 2 + 3 = 5


const calculateSubtract = calculate("SUBTRACT");
calculateSubtract(2, 3);
// Output: 2 - 3 = -1

کارهایی که انجام داده‌ایم به ترتیب زیر است:

  • تابع
    calculate()
    calculate()را فراخوانی کردیم و
    ADD
    ADDرا به عنوان آرگومان به آن ارسال کردیم
  • تابع anonymous بازگشتی را در متغیر
    calalogAdd
    calalogAddذخیره کردیم
  • درنهایت با فراخوانی
    calculateAdd()
    calculateAdd()همراه با آرگومان‌های مورد نیاز، تابع بازگشتی داخلی را فراخوانی کردیم

فراخوانی تابع بازگشتی با استفاده از دو پرانتز

این یک روش بسیار پیچیده برای فراخوانی تابع بازگشتی داخلی است. در این روش از دو پرانتز

()()
()()استفاده می‌کنیم.

برای اینکه آن را به خوبی درک کنیم مثال زیر را بررسی می‌کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
calculate("ADD")(2, 3);
// Output: 2 + 3 = 5
calculate("SUBTRACT")(2, 3);
// Output: 2 - 3 = -1
calculate("ADD")(2, 3); // Output: 2 + 3 = 5 calculate("SUBTRACT")(2, 3); // Output: 2 - 3 = -1
calculate("ADD")(2, 3);
// Output: 2 + 3 = 5

calculate("SUBTRACT")(2, 3);
// Output: 2 - 3 = -1

در مورد این روش می‌توانیم به روشی مشابه با مثال زنجیره‌ای که در بالا داشتیم فکر کنیم. فقط در اینجا به جای زنجیره کردن توابع به یکدیگر، آرگومان‌ها را به هم زنجیره می‌کنیم.

آرگومان‌های پرانتز اول به تابع خارجی تعلق دارند، در حالی که آرگومان‌های پرانتز دوم متعلق به تابع بازگشتی داخلی هستند.

همانطور که قبلا توضیح دادیم متد

calculate()
calculate()یک تابع را برمی‌گرداند و آن همان تابع بازگشتی است که بلافاصله با استفاده از پرانتز دوم فراخوانی می‌شود.

یکی از جاهایی که می‌توانیم این نوع نمادگذاری دو پرانتز را ببینیم، متد

connect
connectدر کتابخانه مدیریت state مربوط به redux است. در این لینک می‌توانید بیشتر در مورد connect مطالعه کنید.

دیدگاه‌ها:

افزودن دیدگاه جدید