Антипаттерны наследования
Антипаттерн 1: изменение Object.prototype
Никогда не добавляй методы в Object.prototype. Это «отравляет» все объекты в программе.
// ПЛОХО: не делай так никогда!
Object.prototype.log = function() {
console.log(this)
}
const obj = { x: 1 }
obj.log() // работает, но...
// for...in теперь включает 'log' для ВСЕХ объектов
for (const key in { a: 1 }) {
console.log(key) // "a", "log" — лишнее!
}
// Конфликты с библиотеками, с браузерными API, с будущими стандартами
Изменение встроенных прототипов (
Object.prototype, Array.prototype, String.prototype) — серьёзная ошибка. Это называется **monkey patching** и может сломать любую библиотеку или код, который ожидает «чистые» прототипы.Антипаттерн 2: глубокие цепочки наследования
// Глубокая иерархия — признак плохого дизайна
class A { methodA() {} }
class B extends A { methodB() {} }
class C extends B { methodC() {} }
class D extends C { methodD() {} }
class E extends D { methodE() {} }
// Проблемы:
// 1. Производительность: поиск метода идёт по 5 уровням
// 2. Хрупкость: изменение A ломает B, C, D, E
// 3. Жёсткая связность: сложно переиспользовать части
// 4. Нарушение принципа единственной ответственности
// Лучше: композиция вместо наследования
class Component {
constructor({ serializer, logger, validator }) {
this.serializer = serializer
this.logger = logger
this.validator = validator
}
}
Антипаттерн 3: instanceof-ловушки
instanceof проверяет цепочку прототипов в момент вызова. Это может давать неожиданные результаты:
// Ловушка 1: разные области видимости (iframes, realm)
const arr = new window.frames[0].Array()
arr instanceof Array // false! Это Array из другого iframe
// Ловушка 2: изменение прототипа после создания
function Foo() {}
const obj = new Foo()
Foo.prototype = {} // заменяем прототип
obj instanceof Foo // false! Прототип obj уже не равен новому Foo.prototype
// Ловушка 3: примитивы
'строка' instanceof String // false (примитив, не объект)
new String('строка') instanceof String // true (объект-обёртка)
Предпочтительные альтернативы:
// Проверка через duck typing
function isIterable(obj) {
return obj != null && typeof obj[Symbol.iterator] === 'function'
}
// Проверка тега
Object.prototype.toString.call([]) // "[object Array]"
Object.prototype.toString.call({}) // "[object Object]"
Array.isArray([]) // true — надёжнее instanceof Array
Антипаттерн 4: смешивание прототипного и классового подходов
// ПЛОХО: непоследовательное использование
function Animal(name) {
this.name = name
}
class Dog extends Animal { // extends с function-конструктором — работает, но...
bark() { console.log('Гав') }
}
// ...создаёт путаницу. Лучше:
// Если используешь классы — только классы
class Animal2 {
constructor(name) { this.name = name }
}
class Dog2 extends Animal2 {
bark() { console.log('Гав') }
}
Антипаттерн 5: неверное копирование объектов с прототипами
const original = Object.create({ method() {} })
original.data = [1, 2, 3]
// Поверхностное копирование — теряет прототип
const bad1 = { ...original } // только собственные свойства, прототип — Object.prototype
const bad2 = Object.assign({}, original) // то же самое
// Если прототип важен:
const good = Object.create(
Object.getPrototypeOf(original),
Object.getOwnPropertyDescriptors(original)
)
Паттерн: предпочитай композицию наследованию
// Вместо глубокой иерархии — собирай объекты из частей
const canSwim = {
swim() { console.log(this.name + ' плывёт') }
}
const canFly = {
fly() { console.log(this.name + ' летит') }
}
function createDuck(name) {
return Object.assign(
Object.create(null),
canSwim,
canFly,
{ name }
)
}
const duck = createDuck('Дональд')
duck.swim() // "Дональд плывёт"
duck.fly() // "Дональд летит"