До ES6 весь JavaScript выполнялся в глобальной области видимости. Представь большой React-проект: если все функции объявлены глобально, случайно назвать два formatDate в разных файлах — и они перезапишут друг друга. Скрипты нужно подключать в строгом порядке. Сопровождать это — кошмар.
Модули ES6 изолируют каждый файл в собственной области видимости. Зависимости явные: сразу видно, что откуда берётся.
// utils/format.js — несколько экспортов из одного файла
export function formatPrice(amount, currency = '₽') {
return amount.toLocaleString('ru-RU') + ' ' + currency
}
export function formatDate(date) {
return new Date(date).toLocaleDateString('ru-RU')
}
export const TAX_RATE = 0.2// Импорт именованных экспортов — фигурные скобки обязательны
import { formatPrice, formatDate, TAX_RATE } from './utils/format.js'
// Переименование при импорте
import { formatPrice as price } from './utils/format.js'
// Импорт всего под неймспейсом
import * as Format from './utils/format.js'
Format.formatPrice(1000) // '1 000 ₽'// services/UserService.js — один default export на файл
export default class UserService {
constructor(apiClient) {
this.api = apiClient
}
async getUser(id) {
return this.api.get(`/users/${id}`)
}
}// Импорт default — без фигурных скобок, имя любое
import UserService from './services/UserService.js'
import MyUserService from './services/UserService.js' // тоже работает// api/client.js
export default class ApiClient { ... } // основной экспорт
export function createClient(config) { ... } // вспомогательный
export const DEFAULT_TIMEOUT = 5000import ApiClient, { createClient, DEFAULT_TIMEOUT } from './api/client.js'// utils/index.js — агрегирует все утилиты
export { formatPrice, formatDate } from './format.js'
export { validateEmail, validatePhone } from './validate.js'
export { default as ApiClient } from './api.js'// Теперь можно импортировать из одного места
import { formatPrice, validateEmail, ApiClient } from './utils'
// Вместо трёх отдельных импортов// Загружается только когда нужно (code splitting)
async function openEditor() {
// Тяжёлая библиотека загрузится только при клике на "Редактировать"
const { default: Editor } = await import('./components/RichEditor.js')
const editor = new Editor('#container')
}
// Условная загрузка
const { Chart } = await import(
isMobile ? './charts/MobileChart.js' : './charts/DesktopChart.js'
)1. Путают именованный и default экспорт:
// Файл: export default function greet() {}
// Сломано:
import { greet } from './greet.js' // SyntaxError — default нужен без скобок
// Исправлено:
import greet from './greet.js'2. Круговые зависимости (circular imports):
// a.js импортирует из b.js, а b.js импортирует из a.js
// Это может привести к undefined при инициализации — избегай!3. Изменяют импортированные примитивы — они readonly:
// Сломано:
import { count } from './counter.js'
count = 10 // TypeError: Assignment to constant variable
// Исправлено — импортируй функцию, которая изменяет:
import { count, increment } from './counter.js'
increment() // меняет внутри модуляimport Button from './components/Button'import express from 'express' — импорт npm-пакетаsrc/components/index.ts — в любом крупном проекте на React/VueСимуляция модульной архитектуры: утилиты, сервисы, компоненты
// Симуляция модульной структуры (в реальном проекте — отдельные файлы)
// === utils/format.js ===
const Format = {
price: (amount, currency = '₽') =>
amount.toLocaleString('ru-RU') + ' ' + currency,
date: (dateStr) =>
new Date(dateStr).toLocaleDateString('ru-RU', {
day: '2-digit', month: 'long', year: 'numeric',
}),
truncate: (str, maxLen = 50) =>
str.length > maxLen ? str.slice(0, maxLen) + '...' : str,
pluralize: (n, one, few, many) => {
const mod10 = n % 10, mod100 = n % 100
if (mod10 === 1 && mod100 !== 11) return `${n} ${one}`
if (mod10 >= 2 && mod10 <= 4 && (mod100 < 10 || mod100 >= 20)) return `${n} ${few}`
return `${n} ${many}`
},
}
// === utils/validate.js ===
const Validate = {
email: (email) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email),
phone: (phone) => /^\+?[\d\s\-()]{10,}$/.test(phone),
required: (value) => value !== null && value !== undefined && String(value).trim() !== '',
}
// === services/ProductService.js ===
const products = [
{ id: 1, name: 'MacBook Pro 14"', price: 189990, category: 'laptops', stock: 5 },
{ id: 2, name: 'AirPods Pro', price: 24990, category: 'audio', stock: 20 },
{ id: 3, name: 'Magic Mouse', price: 8990, category: 'acc', stock: 15 },
]
const ProductService = {
getAll: () => products,
getById: (id) => products.find(p => p.id === id) ?? null,
getByCategory: (cat) => products.filter(p => p.category === cat),
search: (query) => products.filter(p =>
p.name.toLowerCase().includes(query.toLowerCase())
),
}
// === main.js — "импортируем" через деструктуризацию ===
const { price, date, truncate, pluralize } = Format
const { email: validateEmail } = Validate
const { getAll, search } = ProductService
// Используем
console.log(price(189990)) // '189 990 ₽'
console.log(date('2024-01-15')) // '15 января 2024 г.'
console.log(truncate('Очень длинное название товара в каталоге', 25)) // 'Очень длинное название то...'
console.log(pluralize(3, 'товар', 'товара', 'товаров')) // '3 товара'
console.log(validateEmail('ivan@mail.ru')) // true
console.log(validateEmail('notanemail')) // false
const results = search('pro')
console.log(`Найдено: ${results.length} товар(а)`)
results.forEach(p => console.log(` ${p.name} — ${price(p.price)}`))До ES6 весь JavaScript выполнялся в глобальной области видимости. Представь большой React-проект: если все функции объявлены глобально, случайно назвать два formatDate в разных файлах — и они перезапишут друг друга. Скрипты нужно подключать в строгом порядке. Сопровождать это — кошмар.
Модули ES6 изолируют каждый файл в собственной области видимости. Зависимости явные: сразу видно, что откуда берётся.
// utils/format.js — несколько экспортов из одного файла
export function formatPrice(amount, currency = '₽') {
return amount.toLocaleString('ru-RU') + ' ' + currency
}
export function formatDate(date) {
return new Date(date).toLocaleDateString('ru-RU')
}
export const TAX_RATE = 0.2// Импорт именованных экспортов — фигурные скобки обязательны
import { formatPrice, formatDate, TAX_RATE } from './utils/format.js'
// Переименование при импорте
import { formatPrice as price } from './utils/format.js'
// Импорт всего под неймспейсом
import * as Format from './utils/format.js'
Format.formatPrice(1000) // '1 000 ₽'// services/UserService.js — один default export на файл
export default class UserService {
constructor(apiClient) {
this.api = apiClient
}
async getUser(id) {
return this.api.get(`/users/${id}`)
}
}// Импорт default — без фигурных скобок, имя любое
import UserService from './services/UserService.js'
import MyUserService from './services/UserService.js' // тоже работает// api/client.js
export default class ApiClient { ... } // основной экспорт
export function createClient(config) { ... } // вспомогательный
export const DEFAULT_TIMEOUT = 5000import ApiClient, { createClient, DEFAULT_TIMEOUT } from './api/client.js'// utils/index.js — агрегирует все утилиты
export { formatPrice, formatDate } from './format.js'
export { validateEmail, validatePhone } from './validate.js'
export { default as ApiClient } from './api.js'// Теперь можно импортировать из одного места
import { formatPrice, validateEmail, ApiClient } from './utils'
// Вместо трёх отдельных импортов// Загружается только когда нужно (code splitting)
async function openEditor() {
// Тяжёлая библиотека загрузится только при клике на "Редактировать"
const { default: Editor } = await import('./components/RichEditor.js')
const editor = new Editor('#container')
}
// Условная загрузка
const { Chart } = await import(
isMobile ? './charts/MobileChart.js' : './charts/DesktopChart.js'
)1. Путают именованный и default экспорт:
// Файл: export default function greet() {}
// Сломано:
import { greet } from './greet.js' // SyntaxError — default нужен без скобок
// Исправлено:
import greet from './greet.js'2. Круговые зависимости (circular imports):
// a.js импортирует из b.js, а b.js импортирует из a.js
// Это может привести к undefined при инициализации — избегай!3. Изменяют импортированные примитивы — они readonly:
// Сломано:
import { count } from './counter.js'
count = 10 // TypeError: Assignment to constant variable
// Исправлено — импортируй функцию, которая изменяет:
import { count, increment } from './counter.js'
increment() // меняет внутри модуляimport Button from './components/Button'import express from 'express' — импорт npm-пакетаsrc/components/index.ts — в любом крупном проекте на React/VueСимуляция модульной архитектуры: утилиты, сервисы, компоненты
// Симуляция модульной структуры (в реальном проекте — отдельные файлы)
// === utils/format.js ===
const Format = {
price: (amount, currency = '₽') =>
amount.toLocaleString('ru-RU') + ' ' + currency,
date: (dateStr) =>
new Date(dateStr).toLocaleDateString('ru-RU', {
day: '2-digit', month: 'long', year: 'numeric',
}),
truncate: (str, maxLen = 50) =>
str.length > maxLen ? str.slice(0, maxLen) + '...' : str,
pluralize: (n, one, few, many) => {
const mod10 = n % 10, mod100 = n % 100
if (mod10 === 1 && mod100 !== 11) return `${n} ${one}`
if (mod10 >= 2 && mod10 <= 4 && (mod100 < 10 || mod100 >= 20)) return `${n} ${few}`
return `${n} ${many}`
},
}
// === utils/validate.js ===
const Validate = {
email: (email) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email),
phone: (phone) => /^\+?[\d\s\-()]{10,}$/.test(phone),
required: (value) => value !== null && value !== undefined && String(value).trim() !== '',
}
// === services/ProductService.js ===
const products = [
{ id: 1, name: 'MacBook Pro 14"', price: 189990, category: 'laptops', stock: 5 },
{ id: 2, name: 'AirPods Pro', price: 24990, category: 'audio', stock: 20 },
{ id: 3, name: 'Magic Mouse', price: 8990, category: 'acc', stock: 15 },
]
const ProductService = {
getAll: () => products,
getById: (id) => products.find(p => p.id === id) ?? null,
getByCategory: (cat) => products.filter(p => p.category === cat),
search: (query) => products.filter(p =>
p.name.toLowerCase().includes(query.toLowerCase())
),
}
// === main.js — "импортируем" через деструктуризацию ===
const { price, date, truncate, pluralize } = Format
const { email: validateEmail } = Validate
const { getAll, search } = ProductService
// Используем
console.log(price(189990)) // '189 990 ₽'
console.log(date('2024-01-15')) // '15 января 2024 г.'
console.log(truncate('Очень длинное название товара в каталоге', 25)) // 'Очень длинное название то...'
console.log(pluralize(3, 'товар', 'товара', 'товаров')) // '3 товара'
console.log(validateEmail('ivan@mail.ru')) // true
console.log(validateEmail('notanemail')) // false
const results = search('pro')
console.log(`Найдено: ${results.length} товар(а)`)
results.forEach(p => console.log(` ${p.name} — ${price(p.price)}`))Ты строишь утилитарный слой для e-commerce приложения. Создай три объекта-"модуля": 1. `MathUtils` — `sum(arr)`, `average(arr)`, `clamp(val, min, max)` 2. `StringUtils` — `capitalize(str)`, `truncate(str, len)`, `slugify(str)` (пробелы → дефисы, строчные) 3. `ArrayUtils` — `unique(arr)`, `groupBy(arr, key)`, `sortBy(arr, key)` Деструктурируй функции из "модулей" и используй их для обработки данных каталога товаров.
average: MathUtils.sum(arr) / arr.length. clamp: Math.min(Math.max(val, min), max). capitalize: str[0].toUpperCase() + str.slice(1). truncate: str.length > len ? str.slice(0, len) + "..." : str. slugify: str.toLowerCase().replace(/\s+/g, "-"). sortBy: [...arr].sort((a, b) => a[key] > b[key] ? 1 : -1).