← Курс/Namespace: пространства имён#183 из 257+20 XP

Namespace: пространства имён

Что такое namespace

Namespace (пространство имён) — способ группировки связанного кода под единым именем, предотвращающий конфликты имён. В TypeScript namespace компилируется в IIFE-паттерн (Immediately Invoked Function Expression).

namespace Validation {
  export interface StringValidator {
    isValid(s: string): boolean
  }

  export class LettersOnlyValidator implements StringValidator {
    isValid(s: string): boolean {
      return /^[A-Za-z]+$/.test(s)
    }
  }

  export class NumberValidator implements StringValidator {
    isValid(s: string): boolean {
      return /^d+$/.test(s)
    }
  }
}

// Использование через точечную нотацию:
const validator = new Validation.LettersOnlyValidator()
console.log(validator.isValid('Hello'))  // true

Компиляция namespace в JavaScript

TypeScript компилирует namespace в IIFE:

// Скомпилированный вывод:
var Validation;
(function (Validation) {
  class LettersOnlyValidator {
    isValid(s) { return /^[A-Za-z]+$/.test(s) }
  }
  Validation.LettersOnlyValidator = LettersOnlyValidator
})(Validation || (Validation = {}))

Вложенные namespace

namespace App {
  export namespace Models {
    export interface User {
      id: number
      name: string
    }

    export interface Product {
      id: number
      price: number
    }
  }

  export namespace Services {
    export class UserService {
      private users: Models.User[] = []

      add(user: Models.User): void {
        this.users.push(user)
      }

      findById(id: number): Models.User | undefined {
        return this.users.find(u => u.id === id)
      }
    }
  }
}

const service = new App.Services.UserService()
service.add({ id: 1, name: 'Алексей' })

Alias для длинных namespace

import UserService = App.Services.UserService
import User = App.Models.User

const svc = new UserService()  // не нужно писать App.Services.UserService

Namespace vs Module — главное отличие

| | Namespace | Module (ES Module) |

|---|---|---|

| Синтаксис | namespace Foo { } | export / import |

| Применение | Один файл или /// <reference> | Отдельные файлы |

| Рекомендуется | В .d.ts файлах | В современном коде |

| Tree-shaking | Нет | Да |

**Современная практика**: для нового кода используйте ES-модули (import/export). Namespace остался актуален для:

1. Файлов объявлений (.d.ts) — описание глобальных библиотек

2. Augmentation глобальных типов

3. Организации типов внутри одного .d.ts файла

namespace в .d.ts файлах

// Типичное использование: глобальная библиотека (jQuery, _)
// jquery.d.ts
declare namespace jQuery {
  function ajax(url: string, settings?: JQueryAjaxSettings): void
  function get(url: string): JQueryXHR

  interface JQueryAjaxSettings {
    method?: string
    data?: any
  }

  interface JQueryXHR {
    done(callback: (data: any) => void): JQueryXHR
    fail(callback: (error: any) => void): JQueryXHR
  }
}

// Использование:
jQuery.ajax('/api/users')

Примеры

IIFE-паттерн как аналог namespace: группировка кода без загрязнения глобального пространства имён

// TypeScript namespace компилируется в IIFE-паттерн.
// Покажем тот же паттерн в чистом JavaScript.

// Аналог namespace Validation { ... }
const Validation = (function() {
  // Приватные утилиты (не экспортируются)
  function escapeRegex(s) {
    return s.split('').map(function(c) {
      var specials = ['.', '+', '?', '^', '$', '{', '}', '(', ')', '|', '[', ']']
      return specials.indexOf(c) >= 0 ? '_' + c : c
    }).join('')
  }

  // Публичные (аналог export в namespace)
  class LettersOnlyValidator {
    isValid(s) { return /^[A-Za-zА-Яа-яЁё]+$/u.test(s) }
    get description() { return 'только буквы' }
  }

  class EmailValidator {
    isValid(s) { return /^[^s@]+@[^s@]+.[^s@]+$/.test(s) }
    get description() { return 'email адрес' }
  }

  class RangeValidator {
    constructor(min, max) {
      this.min = min
      this.max = max
    }
    isValid(n) {
      return typeof n === 'number' && n >= this.min && n <= this.max
    }
    get description() { return `число от ${this.min} до ${this.max}` }
  }

  function createPatternValidator(pattern, description) {
    return {
      isValid: (s) => pattern.test(s),
      description,
    }
  }

  return {
    LettersOnlyValidator,
    EmailValidator,
    RangeValidator,
    createPatternValidator,
  }
})()

// Аналог вложенного namespace App.Models, App.Services
const App = (function() {
  // namespace Models
  const Models = Object.freeze({
    createUser(id, name, email) {
      return Object.freeze({ id, name, email, createdAt: new Date() })
    },
    createProduct(id, name, price) {
      if (price < 0) throw new RangeError('Цена не может быть отрицательной')
      return Object.freeze({ id, name, price })
    },
  })

  // namespace Services
  const Services = (function() {
    class UserService {
      #users = new Map()

      add(user) {
        if (this.#users.has(user.id)) {
          throw new Error(`Пользователь с id=${user.id} уже существует`)
        }
        this.#users.set(user.id, user)
        return this
      }

      findById(id) {
        return this.#users.get(id) ?? null
      }

      findAll() {
        return [...this.#users.values()]
      }

      get count() { return this.#users.size }
    }

    class ProductService {
      #products = []

      add(product) {
        this.#products.push(product)
        return this
      }

      findByPriceRange(min, max) {
        return this.#products.filter(p => p.price >= min && p.price <= max)
      }

      get count() { return this.#products.length }
    }

    return { UserService, ProductService }
  })()

  return { Models, Services }
})()

// --- Демонстрация ---

console.log('=== Validation namespace ===')
const emailValidator = new Validation.EmailValidator()
const letterValidator = new Validation.LettersOnlyValidator()
const ageValidator = new Validation.RangeValidator(0, 150)

const testCases = [
  { value: 'user@example.com', validator: emailValidator },
  { value: 'not-an-email',     validator: emailValidator },
  { value: 'Алексей',          validator: letterValidator },
  { value: 'Alex123',          validator: letterValidator },
  { value: 25,                  validator: ageValidator },
  { value: 200,                 validator: ageValidator },
]

testCases.forEach(({ value, validator }) => {
  const result = validator.isValid(value) ? 'OK' : 'ОШИБКА'
  console.log(`${result}: "${value}" (${validator.description})`)
})

console.log('\n=== App.Models namespace ===')
const user    = App.Models.createUser(1, 'Алексей', 'alex@mail.ru')
const product = App.Models.createProduct(1, 'Ноутбук', 50000)
console.log('User:', user)
console.log('Product:', product)

console.log('\n=== App.Services namespace ===')
const userSvc = new App.Services.UserService()
userSvc.add(user)
       .add(App.Models.createUser(2, 'Ольга', 'olga@mail.ru'))

console.log('Пользователей:', userSvc.count)
console.log('Найден:', userSvc.findById(1)?.name)
console.log('Все:', userSvc.findAll().map(u => u.name))

const productSvc = new App.Services.ProductService()
productSvc.add(product)
          .add(App.Models.createProduct(2, 'Телефон', 25000))
          .add(App.Models.createProduct(3, 'Планшет', 35000))

const affordable = productSvc.findByPriceRange(20000, 40000)
console.log('\nТовары 20к-40к:', affordable.map(p => p.name))