Урок 06

call, apply, bind

Методы функций для управления this.

call

Вызывает функцию с указанным this и аргументами через запятую:

function greet(greeting, punctuation) {
    return `${greeting}, ${this.name}${punctuation}`;
}

const user = { name: 'Алиса' };

greet.call(user, 'Привет', '!'); // 'Привет, Алиса!'

apply

То же, но аргументы — массив:

greet.apply(user, ['Привет', '!']); // 'Привет, Алиса!'

bind

Возвращает новую функцию с привязанным this:

const boundGreet = greet.bind(user);
boundGreet('Привет', '!'); // 'Привет, Алиса!'

Можно привязать и аргументы:

const greetAlice = greet.bind(user, 'Привет');
greetAlice('!'); // 'Привет, Алиса!'

Потеря this

Без привязки:

const user = {
    name: 'Алиса',
    greet() {
        return this.name;
    },
};

const fn = user.greet;
fn(); // ошибка: this = undefined

С bind:

const fn = user.greet.bind(user);
fn(); // 'Алиса'

call для заимствования методов

Использовать метод массива на array-like:

function args() {
    return Array.prototype.slice.call(arguments);
}

Или проще:

function args(...rest) {
    return rest;
}

apply для spread

До появления spread:

Math.max.apply(null, [1, 2, 3]); // 3

Сейчас:

Math.max(...[1, 2, 3]);

bind в обработчиках

class Counter {
    count = 0;

    increment() {
        this.count++;
    }
}

const counter = new Counter();

// Так не работает
button.addEventListener('click', counter.increment);

// С bind
button.addEventListener('click', counter.increment.bind(counter));

// Или стрелочная
button.addEventListener('click', () => counter.increment());

bind возвращает новую функцию

function fn() {}
const bound = fn.bind(null);

fn === bound; // false

Каждый bind создаёт новую функцию. Учитывай в оптимизациях.

Двойной bind

Повторный bind не меняет this:

const bound1 = fn.bind(a);
const bound2 = bound1.bind(b); // this всё ещё a

Это фича: bind «запечатывает» контекст.

Современные альтернативы

  • Стрелочные функции — для захвата this
  • Классовые поля — increment = () => {}
  • Стрелочные методы — автоматически захватывают this

Практика: декоратор

function log(target, key, descriptor) {
    const original = descriptor.value;

    descriptor.value = function(...args) {
        console.log(`${key} вызван с`, args);
        return original.apply(this, args);
    };

    return descriptor;
}

Здесь apply сохраняет this исходного вызова.

Практика: throttle

function throttle(fn, delay) {
    let last = 0;

    return function(...args) {
        const now = Date.now();
        if (now - last >= delay) {
            last = now;
            return fn.apply(this, args);
        }
    };
}

apply(this) передаёт this оригинала.

Когда использовать

  • call — когда известны аргументы
  • apply — когда аргументы в массиве (редко в современном коде)
  • bind — когда нужна функция с фиксированным this

Для большинства задач хватает стрелочных функций и bind.

Итоги

  • call — вызов с this и аргументами
  • apply — вызов с this и массивом
  • bind — новая функция с привязанным this
  • bind «запечатывает» контекст
  • Современные альтернативы — стрелочные функции
  • Полезны в декораторах и обёртках