В финансовом приложении класс BankAccount хранит баланс. Если разработчик случайно сделает account.balance = 99999 — деньги появятся из ниоткуда. Приватные поля #balance делают это физически невозможным: попытка доступа снаружи класса — синтаксическая ошибка ещё до запуска кода.
Поле с префиксом _ — это соглашение между разработчиками: "не трогай снаружи". Технически оно доступно откуда угодно:
class Database {
constructor(url) {
this._connectionString = url // "защищено" по соглашению
this._isConnected = false
}
connect() {
this._isConnected = true
console.log('Подключено к', this._connectionString)
}
}
const db = new Database('postgres://localhost:5432/mydb')
// Технически можно обратиться — защита только на уровне договорённости:
console.log(db._connectionString) // 'postgres://localhost:5432/mydb'Поля с # — это настоящая приватность на уровне языка. Обратиться к ним снаружи класса невозможно — будет ошибка:
class BankAccount {
#balance = 0
#transactions = []
constructor(initialBalance) {
this.#balance = initialBalance
}
deposit(amount) {
if (amount <= 0) throw new Error('Сумма должна быть положительной')
this.#balance += amount
this.#transactions.push({ type: 'deposit', amount, date: new Date() })
}
withdraw(amount) {
if (amount > this.#balance) throw new Error('Недостаточно средств')
this.#balance -= amount
this.#transactions.push({ type: 'withdraw', amount, date: new Date() })
}
get balance() {
return this.#balance // геттер — readonly доступ снаружи
}
get history() {
return [...this.#transactions] // копия — снаружи нельзя изменить оригинал
}
}
const acc = new BankAccount(5000)
acc.deposit(2000)
acc.withdraw(500)
console.log(acc.balance) // 6500 — через геттер
// acc.#balance // SyntaxError — нет доступа!class PaymentProcessor {
#apiKey
constructor(apiKey) {
this.#apiKey = apiKey
}
#validateAmount(amount) { // приватный метод — только для внутреннего использования
if (amount <= 0) throw new Error('Некорректная сумма')
if (amount > 1_000_000) throw new Error('Превышен лимит')
}
charge(amount) {
this.#validateAmount(amount) // публичный метод вызывает приватный
console.log(`Списание ${amount} руб. с ключом ${this.#apiKey.slice(0, 4)}...`)
}
}class IdGenerator {
static #count = 0 // общий счётчик для всех экземпляров
static next() {
return ++IdGenerator.#count
}
static get total() {
return IdGenerator.#count
}
}
IdGenerator.next() // 1
IdGenerator.next() // 2
console.log(IdGenerator.total) // 2
// IdGenerator.#count // SyntaxError| Критерий | _ (конвенция) | # (ES2022) |
|----------|--------------|------------|
| Реальная защита | Нет | Да |
| Доступ в подклассах | Да | Нет (нужен геттер/метод) |
| Поддержка браузеров | Все | Современные (2021+) |
| TypeScript | private / protected | Полная поддержка |
Приватное поле + геттер без сеттера = читаемое, но не изменяемое извне свойство:
class Order {
#id
#status = 'pending'
constructor() {
this.#id = Math.random().toString(36).slice(2).toUpperCase()
}
get id() { return this.#id }
get status() { return this.#status }
confirm() { this.#status = 'confirmed' }
cancel() { this.#status = 'cancelled' }
// Сеттера нет — статус меняется только через методы
}1. Доступ к приватному полю из подкласса:
class Animal {
#name
constructor(name) { this.#name = name }
get name() { return this.#name }
}
class Dog extends Animal {
bark() {
// return this.#name // SyntaxError! # поля не наследуются
return this.name // Правильно: через геттер родителя
}
}2. Попытка сериализации объекта с приватными полями через JSON.stringify:
class User {
#password
name
constructor(name, password) {
this.name = name
this.#password = password
}
}
const user = new User('Иван', 'secret123')
console.log(JSON.stringify(user)) // '{"name":"Иван"}' — #password не включается, это хорошо!
// Но если нужно больше полей в JSON — добавь toJSON():
// toJSON() { return { name: this.name, id: this.#id } }3. Использование # в коде задания с target ES2017 — ошибка компиляции в старых средах:
// В учебных средах с ограниченным ES target
// # поля могут не поддерживаться — используй _prefix конвенцию
// В продакшн с современным target — # всегда предпочтительнееКласс BankAccount с приватными полями #balance и #transactions, геттерами и цепочкой вызовов
class BankAccount {
#balance
#transactions
#owner
constructor(owner, initialBalance = 0) {
this.#owner = owner
this.#balance = initialBalance
this.#transactions = []
}
deposit(amount) {
if (amount <= 0) throw new Error('Сумма пополнения должна быть положительной')
this.#balance += amount
this.#transactions.push({
type: 'deposit',
amount,
balance: this.#balance,
date: new Date().toLocaleDateString('ru-RU'),
})
return this // для цепочки вызовов
}
withdraw(amount) {
if (amount <= 0) throw new Error('Сумма снятия должна быть положительной')
if (amount > this.#balance) throw new Error(`Недостаточно средств. Доступно: ${this.#balance} руб.`)
this.#balance -= amount
this.#transactions.push({
type: 'withdraw',
amount,
balance: this.#balance,
date: new Date().toLocaleDateString('ru-RU'),
})
return this
}
get balance() { return this.#balance }
get owner() { return this.#owner }
get history() { return [...this.#transactions] }
toString() {
return `Счёт [${this.#owner}]: ${this.#balance} руб.`
}
}
const account = new BankAccount('Иван Петров', 10000)
account.deposit(5000).deposit(2000) // цепочка вызовов
account.withdraw(3500)
console.log(account.balance) // 13500
console.log(String(account)) // 'Счёт [Иван Петров]: 13500 руб.'
console.log(account.history.length) // 3
try {
account.withdraw(99999)
} catch (e) {
console.log(e.message) // 'Недостаточно средств. Доступно: 13500 руб.'
}
// Прямой доступ к приватным полям невозможен
// account.#balance = 999999 // SyntaxErrorВ финансовом приложении класс BankAccount хранит баланс. Если разработчик случайно сделает account.balance = 99999 — деньги появятся из ниоткуда. Приватные поля #balance делают это физически невозможным: попытка доступа снаружи класса — синтаксическая ошибка ещё до запуска кода.
Поле с префиксом _ — это соглашение между разработчиками: "не трогай снаружи". Технически оно доступно откуда угодно:
class Database {
constructor(url) {
this._connectionString = url // "защищено" по соглашению
this._isConnected = false
}
connect() {
this._isConnected = true
console.log('Подключено к', this._connectionString)
}
}
const db = new Database('postgres://localhost:5432/mydb')
// Технически можно обратиться — защита только на уровне договорённости:
console.log(db._connectionString) // 'postgres://localhost:5432/mydb'Поля с # — это настоящая приватность на уровне языка. Обратиться к ним снаружи класса невозможно — будет ошибка:
class BankAccount {
#balance = 0
#transactions = []
constructor(initialBalance) {
this.#balance = initialBalance
}
deposit(amount) {
if (amount <= 0) throw new Error('Сумма должна быть положительной')
this.#balance += amount
this.#transactions.push({ type: 'deposit', amount, date: new Date() })
}
withdraw(amount) {
if (amount > this.#balance) throw new Error('Недостаточно средств')
this.#balance -= amount
this.#transactions.push({ type: 'withdraw', amount, date: new Date() })
}
get balance() {
return this.#balance // геттер — readonly доступ снаружи
}
get history() {
return [...this.#transactions] // копия — снаружи нельзя изменить оригинал
}
}
const acc = new BankAccount(5000)
acc.deposit(2000)
acc.withdraw(500)
console.log(acc.balance) // 6500 — через геттер
// acc.#balance // SyntaxError — нет доступа!class PaymentProcessor {
#apiKey
constructor(apiKey) {
this.#apiKey = apiKey
}
#validateAmount(amount) { // приватный метод — только для внутреннего использования
if (amount <= 0) throw new Error('Некорректная сумма')
if (amount > 1_000_000) throw new Error('Превышен лимит')
}
charge(amount) {
this.#validateAmount(amount) // публичный метод вызывает приватный
console.log(`Списание ${amount} руб. с ключом ${this.#apiKey.slice(0, 4)}...`)
}
}class IdGenerator {
static #count = 0 // общий счётчик для всех экземпляров
static next() {
return ++IdGenerator.#count
}
static get total() {
return IdGenerator.#count
}
}
IdGenerator.next() // 1
IdGenerator.next() // 2
console.log(IdGenerator.total) // 2
// IdGenerator.#count // SyntaxError| Критерий | _ (конвенция) | # (ES2022) |
|----------|--------------|------------|
| Реальная защита | Нет | Да |
| Доступ в подклассах | Да | Нет (нужен геттер/метод) |
| Поддержка браузеров | Все | Современные (2021+) |
| TypeScript | private / protected | Полная поддержка |
Приватное поле + геттер без сеттера = читаемое, но не изменяемое извне свойство:
class Order {
#id
#status = 'pending'
constructor() {
this.#id = Math.random().toString(36).slice(2).toUpperCase()
}
get id() { return this.#id }
get status() { return this.#status }
confirm() { this.#status = 'confirmed' }
cancel() { this.#status = 'cancelled' }
// Сеттера нет — статус меняется только через методы
}1. Доступ к приватному полю из подкласса:
class Animal {
#name
constructor(name) { this.#name = name }
get name() { return this.#name }
}
class Dog extends Animal {
bark() {
// return this.#name // SyntaxError! # поля не наследуются
return this.name // Правильно: через геттер родителя
}
}2. Попытка сериализации объекта с приватными полями через JSON.stringify:
class User {
#password
name
constructor(name, password) {
this.name = name
this.#password = password
}
}
const user = new User('Иван', 'secret123')
console.log(JSON.stringify(user)) // '{"name":"Иван"}' — #password не включается, это хорошо!
// Но если нужно больше полей в JSON — добавь toJSON():
// toJSON() { return { name: this.name, id: this.#id } }3. Использование # в коде задания с target ES2017 — ошибка компиляции в старых средах:
// В учебных средах с ограниченным ES target
// # поля могут не поддерживаться — используй _prefix конвенцию
// В продакшн с современным target — # всегда предпочтительнееКласс BankAccount с приватными полями #balance и #transactions, геттерами и цепочкой вызовов
class BankAccount {
#balance
#transactions
#owner
constructor(owner, initialBalance = 0) {
this.#owner = owner
this.#balance = initialBalance
this.#transactions = []
}
deposit(amount) {
if (amount <= 0) throw new Error('Сумма пополнения должна быть положительной')
this.#balance += amount
this.#transactions.push({
type: 'deposit',
amount,
balance: this.#balance,
date: new Date().toLocaleDateString('ru-RU'),
})
return this // для цепочки вызовов
}
withdraw(amount) {
if (amount <= 0) throw new Error('Сумма снятия должна быть положительной')
if (amount > this.#balance) throw new Error(`Недостаточно средств. Доступно: ${this.#balance} руб.`)
this.#balance -= amount
this.#transactions.push({
type: 'withdraw',
amount,
balance: this.#balance,
date: new Date().toLocaleDateString('ru-RU'),
})
return this
}
get balance() { return this.#balance }
get owner() { return this.#owner }
get history() { return [...this.#transactions] }
toString() {
return `Счёт [${this.#owner}]: ${this.#balance} руб.`
}
}
const account = new BankAccount('Иван Петров', 10000)
account.deposit(5000).deposit(2000) // цепочка вызовов
account.withdraw(3500)
console.log(account.balance) // 13500
console.log(String(account)) // 'Счёт [Иван Петров]: 13500 руб.'
console.log(account.history.length) // 3
try {
account.withdraw(99999)
} catch (e) {
console.log(e.message) // 'Недостаточно средств. Доступно: 13500 руб.'
}
// Прямой доступ к приватным полям невозможен
// account.#balance = 999999 // SyntaxErrorВ системе авторизации нужен класс Password. Реализуй: метод validate(input) — возвращает true если input совпадает с паролем; геттер strength — возвращает "weak" (длина < 6), "medium" (6-11 символов), "strong" (12+ символов и содержит цифры). Примечание: в starterCode используй обычное свойство _value вместо #value для совместимости с sandbox.
this._value = value в конструкторе. validate: return this._value === input. strength: проверь длину через .length и наличие цифры через /\d/.test(this._value). strong требует length >= 12 И наличие цифры (hasDigit).