Seriya: JavaScript Basics Dars 21

Higher-Order Function

HOF nima, First-Class Function tushunchasi, map/filter/reduce qanday ishlaydi, function composition va V8 ning JIT optimallashtirishi.

21-dars

Higher-Order Function

Avval — Higher-Order Function nima

Higher-Order Function (HOF) — quyidagi ikki shartdan kamida bittasini bajaradigan funksiya:

  1. Boshqa funksiyani ARGUMENT sifatida qabul qiladi
  2. Funksiya QAYTARADI (return qiladi)

Bu mumkin bo'lishining sababi — JS da funksiyalar First-Class Citizen (birinchi darajali fuqaro).


First-Class Function — nima uchun bu mumkin

JS da funksiya — bu shunchaki qiymat (value), xuddi son, string, obyekt kabi.

const num = 5;              // qiymat
const str = 'salom';        // qiymat
const fn  = function() {};  // BU HAM QIYMAT!

Qiymat bo'lgani uchun funksiya:

// 1. O'zgaruvchiga berilishi mumkin:
const greet = function() { console.log('salom'); };

// 2. Argument sifatida berilishi mumkin:
function callTwice(fn) {
  fn();
  fn();
}
callTwice(greet);

// 3. Qaytarilishi mumkin:
function makeGreeter() {
  return function() { console.log('salom'); };
}

// 4. Array/obyekt ichida saqlanishi mumkin:
const fns = [greet, function() {}, () => {}];

Boshqa tillarda (masalan, eski Java versiyalari) funksiya "birinchi darajali" emas — uni to'g'ridan-to'g'ri argument qilib berib bo'lmaydi, faqat maxsus interfeys orqali mumkin. JS da esa funksiya — oddiy qiymat, shu sabab HOF tabiiy ravishda ishlaydi.


V8 ichida funksiya "qiymat" sifatida qanday yashaydi

const fn = function() { console.log('salom'); };

V8 uchun fn — bu heap dagi obyektga pointer. Funksiya obyekti o'zida:

Function Object (heap da) {
  [[Call]]: <bytecode/machine code>
  [[Environment]]: → yaratilgan joyning Lexical Environment
  name: "fn"
  length: 0  (parametrlar soni)
}

fn o'zgaruvchisi — bu obyektga ishora (reference), xuddi obyekt yoki array kabi. Shuning uchun uni boshqa o'zgaruvchiga ko'chirish, argument qilib berish — bu shunchaki pointer nusxalash, oddiy son yoki string ko'chirishdan farqi yo'q V8 uchun.


1-tur: Funksiyani ARGUMENT sifatida qabul qilish

Eng oddiy misol — o'zimiz yozamiz

function callTwice(fn) {
  fn();
  fn();
}

function sayHi() {
  console.log('Salom!');
}

callTwice(sayHi);
// Salom!
// Salom!

callTwice — bu Higher-Order Function, chunki u fn degan argument sifatida boshqa funksiyani qabul qiladi.

V8 ichida nima bo'ladi

callTwice(sayHi) chaqirildi:

Execution Context: callTwice
LexicalEnvironment: {
  fn: <pointer → sayHi funksiya obyekti>
}

fn() chaqirilganda V8 fn ning qiymatini (pointer) oladi, u ko'rsatgan funksiya obyektining [[Call]] qismini ishga tushiradi. Bu jarayon oddiy funksiya chaqirish bilan bir xil — fn qayerdan kelgani (argument orqalimi, to'g'ridan-to'g'rimi) V8 uchun farqi yo'q.


Built-in HOF lar — map, filter, reduce

Bu uchtasi JS da eng ko'p ishlatiladigan HOF lar — Array ustida callback funksiya bilan ishlaydi.

map — har bir elementni o'zgartiradi, yangi array qaytaradi

const numbers = [1, 2, 3, 4];

const doubled = numbers.map(function(x) {
  return x * 2;
});

console.log(doubled); // [2, 4, 6, 8]
console.log(numbers); // [1, 2, 3, 4] — o'zgarmadi!

map ichki logikasi (soddalashtirib):

Array.prototype.map = function(callback) {
  const result = [];
  for (let i = 0; i < this.length; i++) {
    result.push(callback(this[i], i, this)); // callback = berilgan funksiya
  }
  return result;
};

callback — bu argument sifatida kelgan funksiya. map uni har bir element uchun bir marta chaqiradi, uchta argument bilan: element, index, original array.

filter — shartga mos elementlarni tanlaydi

const numbers = [1, 2, 3, 4, 5, 6];

const evens = numbers.filter(function(x) {
  return x % 2 === 0;
});

console.log(evens); // [2, 4, 6]

filter ichki logikasi:

Array.prototype.filter = function(callback) {
  const result = [];
  for (let i = 0; i < this.length; i++) {
    if (callback(this[i], i, this)) { // true bo'lsa qo'shadi
      result.push(this[i]);
    }
  }
  return result;
};

reduce — barcha elementlarni bitta qiymatga "yig'adi"

const numbers = [1, 2, 3, 4];

const sum = numbers.reduce(function(acc, current) {
  return acc + current;
}, 0); // 0 — boshlang'ich qiymat

console.log(sum); // 10

Step-by-step nima bo'ladi:

acc=0, current=1 → return 0+1=1
acc=1, current=2 → return 1+2=3
acc=3, current=3 → return 3+3=6
acc=6, current=4 → return 6+4=10

Natija: 10

reduce ichki logikasi:

Array.prototype.reduce = function(callback, initial) {
  let acc = initial;
  for (let i = 0; i < this.length; i++) {
    acc = callback(acc, this[i], i, this);
  }
  return acc;
};

reduce — eng kuchli HOF, chunki map va filter ni ham reduce orqali yozish mumkin:

// map ni reduce bilan:
const doubled = numbers.reduce((acc, x) => {
  acc.push(x * 2);
  return acc;
}, []);

// filter ni reduce bilan:
const evens = numbers.reduce((acc, x) => {
  if (x % 2 === 0) acc.push(x);
  return acc;
}, []);

2-tur: Funksiya QAYTARADIGAN Higher-Order Function

Oddiy misol

function multiplier(factor) {
  return function(x) {
    return x * factor;
  };
}

const double = multiplier(2);
const triple = multiplier(3);

double(5); // 10
triple(5); // 15

Bu — Closure mavzusida ko'rgan pattern. multiplier — HOF, chunki u funksiya qaytaradi. Qaytarilgan funksiya factor ni closure orqali ushlab qoladi.

V8 ichida — har chaqiruv alohida closure

multiplier(2) chaqirildi:
  Execution Context: multiplier
  LexicalEnvironment: { factor: 2 }
  Qaytarilgan funksiya: [[Environment]] → bu context

multiplier(3) chaqirildi:
  Execution Context: multiplier (YANGI, alohida)
  LexicalEnvironment: { factor: 3 }
  Qaytarilgan funksiya: [[Environment]] → bu YANGI context

double va triple — ikkita mutlaqo alohida closure, ikkita alohida Heap Context ga ishora qiladi.


Function Composition — HOF larni birlashtirish

HOF larning eng kuchli qo'llanilishi — kichik funksiyalarni birlashtirib, katta funksionallik yaratish.

const pipe = (...fns) => (input) => fns.reduce((acc, fn) => fn(acc), input);

const addOne = x => x + 1;
const double = x => x * 2;
const square = x => x ** 2;

const process = pipe(addOne, double, square);

process(3);
// addOne(3) = 4
// double(4) = 8
// square(8) = 64

pipe o'zi — HOF (funksiyalarni argument sifatida oladi, funksiya qaytaradi). Ichida reduce — yana bir HOF, funksiyalar ustidan yuradi.


V8 ning HOF larni optimallashtirish (JIT)

V8 ning JIT compileri callback funksiyalarni Inline Cache orqali optimallashtiradi — agar bir xil callback ko'p marta, bir xil tipdagi argumentlar bilan chaqirilsa, V8 uni monomorphic deb belgilaydi va tezlashtiradi.

// YAXSHI — bir xil tip har doim:
[1, 2, 3].map(x => x * 2); // number lar bilan

// YOMON — aralash tip, V8 deoptimize qiladi:
[1, '2', {}].map(x => x); // number, string, obyekt aralash

Agar callback turli xil argument tiplarini qabul qilsa, V8 polymorphic/megamorphic holatga o'tadi — bu sekinroq, chunki V8 har safar tipni qayta tekshirishi kerak bo'ladi.


Amaliy — nega HOF muhim

1. Kod takrorlanishini kamaytiradi

// HOF siz — har biriga alohida loop:
function doubleAll(arr) {
  const result = [];
  for (let i = 0; i < arr.length; i++) result.push(arr[i] * 2);
  return result;
}

function squareAll(arr) {
  const result = [];
  for (let i = 0; i < arr.length; i++) result.push(arr[i] ** 2);
  return result;
}

// HOF bilan — bitta umumiy pattern:
const doubled = numbers.map(x => x * 2);
const squared = numbers.map(x => x ** 2);

2. Deklarativ kod — "nima" ni tasvirlaydi, "qanday" ni emas

// Imperative — har qadamni aytib berish kerak:
const result = [];
for (let i = 0; i < users.length; i++) {
  if (users[i].age >= 18) {
    result.push(users[i].name);
  }
}

// Declarative — HOF bilan, faqat "nima" kerak:
const result = users
  .filter(user => user.age >= 18)
  .map(user => user.name);

3. Testlash osonroq — funksiyalar kichik, alohida

const isAdult = user => user.age >= 18;
const getName = user => user.name;

users.filter(isAdult).map(getName);

// isAdult va getName ni alohida test qilish mumkin

Xulosa

Higher-Order Function — funksiyani argument sifatida qabul qiladigan yoki funksiya qaytaradigan funksiya. Bu JS da mumkin, chunki funksiyalar First-Class Citizen — ular oddiy qiymatlar kabi (heap dagi obyektga pointer) saqlanadi, uzatiladi, qaytariladi. map, filter, reduce — eng ko'p ishlatiladigan built-in HOF lar, ularning har biri ichida callback funksiyani array elementlari ustidan chaqiradi. HOF lar orqali kod qisqaradi, deklarativ bo'ladi, va kichik, testlanadigan funksiyalarga bo'linadi.