← JavaScript/Статические свойства и методы#95 из 383← ПредыдущийСледующий →+25 XP
Полезно по теме:Гайд: как учить JavaScriptПрактика: JS базаПрактика: async и сетьТермин: Closure

Статические свойства и методы

Какую проблему решают статические методы

Ты разрабатываешь класс Product для интернет-магазина. Нужно создавать объект из разных форматов: из JSON-строки, из CSV-строки, из объекта с другой структурой. Или нужен метод сравнения двух товаров для сортировки — он не привязан к конкретному экземпляру.

Статические методы принадлежат классу, а не экземплярам. Вызываются через имя класса, а не через this.

На основе предыдущих уроков

  • «Классы» — основы синтаксиса классов
  • «Наследование» — static методы наследуются дочерними классами
  • «JSON» — JSON.parse внутри фабричных методов
  • Объявление и вызов

    class MathHelper {
      static PI = 3.14159      // статическое свойство
    
      static square(x) { return x * x }
      static cube(x) { return x * x * x }
    }
    
    // Вызов через класс
    MathHelper.square(4)   // 16
    MathHelper.PI          // 3.14159
    
    // НЕ через экземпляр — они не там живут
    const h = new MathHelper()
    h.square(4)            // TypeError: h.square is not a function

    Главный сценарий: фабричные методы

    Фабричный метод — альтернативный способ создания объекта. Позволяет иметь несколько «конструкторов» с разной логикой:

    class Product {
      constructor(name, price, category) {
        this.name = name
        this.price = price
        this.category = category
      }
    
      // Создать из JSON-строки
      static fromJSON(json) {
        const data = typeof json === 'string' ? JSON.parse(json) : json
        return new Product(data.name, data.price, data.category)
      }
    
      // Создать из CSV-строки "MacBook Pro,189990,laptops"
      static fromCSV(csv) {
        const [name, price, category] = csv.split(',')
        return new Product(name.trim(), Number(price), category.trim())
      }
    
      // Минимальный продукт (вместо Product('Без названия', 0, 'other'))
      static empty() {
        return new Product('Без названия', 0, 'other')
      }
    }
    
    const p1 = Product.fromJSON('{"name":"iPhone","price":89990,"category":"phones"}')
    const p2 = Product.fromCSV('MacBook Pro, 189990, laptops')
    const p3 = Product.empty()

    Статические свойства — счётчики и кеши

    class Request {
      static count = 0
      static totalTime = 0
    
      constructor(url) {
        this.url = url
        Request.count++   // обращаемся через имя класса, не this
      }
    
      static getStats() {
        return {
          count: Request.count,
          avgTime: Request.count > 0 ? Request.totalTime / Request.count : 0,
        }
      }
    }
    
    new Request('/api/users')
    new Request('/api/products')
    console.log(Request.getStats())  // { count: 2, avgTime: 0 }

    Статические компараторы для сортировки

    class User {
      constructor(name, age, rating) {
        this.name = name
        this.age = age
        this.rating = rating
      }
    
      static compareByAge(a, b)    { return a.age - b.age }
      static compareByRating(a, b) { return b.rating - a.rating }  // убывание
      static compareByName(a, b)   { return a.name.localeCompare(b.name) }
    }
    
    const users = [
      new User('Олег', 35, 4.2),
      new User('Анна', 25, 4.8),
      new User('Иван', 30, 4.5),
    ]
    
    users.sort(User.compareByRating)
    console.log(users.map(u => u.name))  // ['Анна', 'Иван', 'Олег']

    Наследование статических методов

    class Shape {
      constructor(color) { this.color = color }
      static create(color) { return new this(color) }  // this — это сам класс!
    }
    
    class Circle extends Shape {
      constructor(color, radius) {
        super(color)
        this.radius = radius
      }
    }
    
    // Shape.create() — создаёт Shape
    // Circle.create() — наследует, создаёт Circle (this = Circle)
    const s = Shape.create('red')
    const c = Circle.create('blue')   // Circle.create унаследован от Shape

    Типичные ошибки

    1. Обращаются к статическому методу через экземпляр:

    // Сломано:
    const p = new Product('iPhone', 89990, 'phones')
    const p2 = p.fromJSON(json)  // TypeError: p.fromJSON is not a function
    
    // Исправлено:
    const p2 = Product.fromJSON(json)  // вызов через класс

    2. В статическом методе используют this как экземпляр:

    // Сломано — this в статическом методе это сам класс, не экземпляр:
    class Product {
      static compareByPrice(a, b) {
        return this.price - b.price  // this не тот экземпляр, что думаешь!
      }
    }
    
    // Исправлено:
    static compareByPrice(a, b) {
      return a.price - b.price  // используй параметры
    }

    3. Хранят мутабельные данные в статических свойствах — общие для всех:

    // Осторожно — cache общий для всех использований класса:
    class Cache {
      static data = {}  // одна копия на весь класс, не на экземпляр!
    }

    В реальных проектах

  • Фабрики: User.fromOAuth(profile), Product.fromAmazonAPI(data)
  • Компараторы: Product.sortByPrice передаётся в .sort()
  • Утилиты: DateUtils.format(), StringUtils.truncate() — чистые функции в классе
  • Singleton: статическое свойство instance — один объект на всё приложение
  • React: Component.getDerivedStateFromProps — статический lifecycle-метод
  • Примеры

    Класс Product с фабричными методами, компараторами и счётчиком

    class Product {
      static count = 0
    
      constructor(name, price, category, inStock = true) {
        this.id = ++Product.count
        this.name = name
        this.price = price
        this.category = category
        this.inStock = inStock
      }
    
      // Фабричный метод из JSON (строка или объект)
      static fromJSON(json) {
        const d = typeof json === 'string' ? JSON.parse(json) : json
        return new Product(d.name, d.price, d.category, d.inStock ?? true)
      }
    
      // Фабричный метод из CSV "MacBook Pro,189990,laptops"
      static fromCSV(csv) {
        const parts = csv.split(',').map(s => s.trim())
        return new Product(parts[0], Number(parts[1]), parts[2])
      }
    
      // Компараторы для сортировки
      static byPriceAsc(a, b)  { return a.price - b.price }
      static byPriceDesc(a, b) { return b.price - a.price }
      static byName(a, b)      { return a.name.localeCompare(b.name, 'ru') }
    
      // Утилиты
      static getTotal(products) {
        return products.reduce((sum, p) => sum + p.price, 0)
      }
    
      static filterAvailable(products) {
        return products.filter(p => p.inStock)
      }
    
      toString() {
        return `[${this.id}] ${this.name} — ${this.price.toLocaleString('ru-RU')} ₽`
      }
    }
    
    // Создание из разных источников
    const p1 = new Product('MacBook Pro', 189990, 'laptops')
    const p2 = Product.fromJSON({ name: 'iPhone 15', price: 89990, category: 'phones' })
    const p3 = Product.fromJSON('{"name":"AirPods Pro","price":24990,"category":"audio"}')
    const p4 = Product.fromCSV('Magic Mouse, 8990, accessories')
    const p5 = new Product('Старый товар', 100, 'misc', false)  // нет в наличии
    
    console.log('Создано товаров:', Product.count)  // 5
    
    const catalog = [p1, p2, p3, p4, p5]
    
    // Доступные товары
    const available = Product.filterAvailable(catalog)
    console.log('В наличии:', available.length)  // 4
    
    // Сортировка по цене
    const sorted = [...available].sort(Product.byPriceDesc)
    console.log('\nПо убыванию цены:')
    sorted.forEach(p => console.log(' ', p.toString()))
    
    // Итого
    const total = Product.getTotal(available)
    console.log('\nСумма каталога:', total.toLocaleString('ru-RU') + ' ₽')

    Статические свойства и методы

    Какую проблему решают статические методы

    Ты разрабатываешь класс Product для интернет-магазина. Нужно создавать объект из разных форматов: из JSON-строки, из CSV-строки, из объекта с другой структурой. Или нужен метод сравнения двух товаров для сортировки — он не привязан к конкретному экземпляру.

    Статические методы принадлежат классу, а не экземплярам. Вызываются через имя класса, а не через this.

    На основе предыдущих уроков

  • «Классы» — основы синтаксиса классов
  • «Наследование» — static методы наследуются дочерними классами
  • «JSON» — JSON.parse внутри фабричных методов
  • Объявление и вызов

    class MathHelper {
      static PI = 3.14159      // статическое свойство
    
      static square(x) { return x * x }
      static cube(x) { return x * x * x }
    }
    
    // Вызов через класс
    MathHelper.square(4)   // 16
    MathHelper.PI          // 3.14159
    
    // НЕ через экземпляр — они не там живут
    const h = new MathHelper()
    h.square(4)            // TypeError: h.square is not a function

    Главный сценарий: фабричные методы

    Фабричный метод — альтернативный способ создания объекта. Позволяет иметь несколько «конструкторов» с разной логикой:

    class Product {
      constructor(name, price, category) {
        this.name = name
        this.price = price
        this.category = category
      }
    
      // Создать из JSON-строки
      static fromJSON(json) {
        const data = typeof json === 'string' ? JSON.parse(json) : json
        return new Product(data.name, data.price, data.category)
      }
    
      // Создать из CSV-строки "MacBook Pro,189990,laptops"
      static fromCSV(csv) {
        const [name, price, category] = csv.split(',')
        return new Product(name.trim(), Number(price), category.trim())
      }
    
      // Минимальный продукт (вместо Product('Без названия', 0, 'other'))
      static empty() {
        return new Product('Без названия', 0, 'other')
      }
    }
    
    const p1 = Product.fromJSON('{"name":"iPhone","price":89990,"category":"phones"}')
    const p2 = Product.fromCSV('MacBook Pro, 189990, laptops')
    const p3 = Product.empty()

    Статические свойства — счётчики и кеши

    class Request {
      static count = 0
      static totalTime = 0
    
      constructor(url) {
        this.url = url
        Request.count++   // обращаемся через имя класса, не this
      }
    
      static getStats() {
        return {
          count: Request.count,
          avgTime: Request.count > 0 ? Request.totalTime / Request.count : 0,
        }
      }
    }
    
    new Request('/api/users')
    new Request('/api/products')
    console.log(Request.getStats())  // { count: 2, avgTime: 0 }

    Статические компараторы для сортировки

    class User {
      constructor(name, age, rating) {
        this.name = name
        this.age = age
        this.rating = rating
      }
    
      static compareByAge(a, b)    { return a.age - b.age }
      static compareByRating(a, b) { return b.rating - a.rating }  // убывание
      static compareByName(a, b)   { return a.name.localeCompare(b.name) }
    }
    
    const users = [
      new User('Олег', 35, 4.2),
      new User('Анна', 25, 4.8),
      new User('Иван', 30, 4.5),
    ]
    
    users.sort(User.compareByRating)
    console.log(users.map(u => u.name))  // ['Анна', 'Иван', 'Олег']

    Наследование статических методов

    class Shape {
      constructor(color) { this.color = color }
      static create(color) { return new this(color) }  // this — это сам класс!
    }
    
    class Circle extends Shape {
      constructor(color, radius) {
        super(color)
        this.radius = radius
      }
    }
    
    // Shape.create() — создаёт Shape
    // Circle.create() — наследует, создаёт Circle (this = Circle)
    const s = Shape.create('red')
    const c = Circle.create('blue')   // Circle.create унаследован от Shape

    Типичные ошибки

    1. Обращаются к статическому методу через экземпляр:

    // Сломано:
    const p = new Product('iPhone', 89990, 'phones')
    const p2 = p.fromJSON(json)  // TypeError: p.fromJSON is not a function
    
    // Исправлено:
    const p2 = Product.fromJSON(json)  // вызов через класс

    2. В статическом методе используют this как экземпляр:

    // Сломано — this в статическом методе это сам класс, не экземпляр:
    class Product {
      static compareByPrice(a, b) {
        return this.price - b.price  // this не тот экземпляр, что думаешь!
      }
    }
    
    // Исправлено:
    static compareByPrice(a, b) {
      return a.price - b.price  // используй параметры
    }

    3. Хранят мутабельные данные в статических свойствах — общие для всех:

    // Осторожно — cache общий для всех использований класса:
    class Cache {
      static data = {}  // одна копия на весь класс, не на экземпляр!
    }

    В реальных проектах

  • Фабрики: User.fromOAuth(profile), Product.fromAmazonAPI(data)
  • Компараторы: Product.sortByPrice передаётся в .sort()
  • Утилиты: DateUtils.format(), StringUtils.truncate() — чистые функции в классе
  • Singleton: статическое свойство instance — один объект на всё приложение
  • React: Component.getDerivedStateFromProps — статический lifecycle-метод
  • Примеры

    Класс Product с фабричными методами, компараторами и счётчиком

    class Product {
      static count = 0
    
      constructor(name, price, category, inStock = true) {
        this.id = ++Product.count
        this.name = name
        this.price = price
        this.category = category
        this.inStock = inStock
      }
    
      // Фабричный метод из JSON (строка или объект)
      static fromJSON(json) {
        const d = typeof json === 'string' ? JSON.parse(json) : json
        return new Product(d.name, d.price, d.category, d.inStock ?? true)
      }
    
      // Фабричный метод из CSV "MacBook Pro,189990,laptops"
      static fromCSV(csv) {
        const parts = csv.split(',').map(s => s.trim())
        return new Product(parts[0], Number(parts[1]), parts[2])
      }
    
      // Компараторы для сортировки
      static byPriceAsc(a, b)  { return a.price - b.price }
      static byPriceDesc(a, b) { return b.price - a.price }
      static byName(a, b)      { return a.name.localeCompare(b.name, 'ru') }
    
      // Утилиты
      static getTotal(products) {
        return products.reduce((sum, p) => sum + p.price, 0)
      }
    
      static filterAvailable(products) {
        return products.filter(p => p.inStock)
      }
    
      toString() {
        return `[${this.id}] ${this.name} — ${this.price.toLocaleString('ru-RU')} ₽`
      }
    }
    
    // Создание из разных источников
    const p1 = new Product('MacBook Pro', 189990, 'laptops')
    const p2 = Product.fromJSON({ name: 'iPhone 15', price: 89990, category: 'phones' })
    const p3 = Product.fromJSON('{"name":"AirPods Pro","price":24990,"category":"audio"}')
    const p4 = Product.fromCSV('Magic Mouse, 8990, accessories')
    const p5 = new Product('Старый товар', 100, 'misc', false)  // нет в наличии
    
    console.log('Создано товаров:', Product.count)  // 5
    
    const catalog = [p1, p2, p3, p4, p5]
    
    // Доступные товары
    const available = Product.filterAvailable(catalog)
    console.log('В наличии:', available.length)  // 4
    
    // Сортировка по цене
    const sorted = [...available].sort(Product.byPriceDesc)
    console.log('\nПо убыванию цены:')
    sorted.forEach(p => console.log(' ', p.toString()))
    
    // Итого
    const total = Product.getTotal(available)
    console.log('\nСумма каталога:', total.toLocaleString('ru-RU') + ' ₽')

    Задание

    Ты разрабатываешь класс для работы с валютой в финтех-приложении. Создай класс `Money` с: - Полем `amount` (число) и `currency` (строка: 'RUB', 'USD', 'EUR') - Статическими фабричными методами: `rub(amount)`, `usd(amount)`, `eur(amount)` - Статическим методом `convert(money, toCurrency, rates)` — конвертация по курсу - Методом экземпляра `format()` — форматирует как "1 000 ₽", "100 $", "85 €" Таблица курсов: `rates = { RUB: 1, USD: 90, EUR: 100 }` (относительно рубля).

    Подсказка

    static usd(amount): return new Money(amount, "USD"). convert: inRub = money.amount * rates[money.currency], converted = inRub / rates[toCurrency]. format: сначала символ из объекта symbols, затем toLocaleString.

    Загружаем среду выполнения...
    Загружаем AI-помощника...