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:
- Boshqa funksiyani ARGUMENT sifatida qabul qiladi
- 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.