instanceof и примеси #
instanceof проверяет, есть ли в цепочке прототипов объекта prototype указанного конструктора (или класса). Примеси (mixins) — способ добавить нескольким классам общее поведение через копирование методов, когда единственное наследование extends не хватает.
Как работает instanceof #
class Lesson {
constructor(title) {
this.title = title
}
}
class VideoLesson extends Lesson {
constructor(title, sec) {
super(title)
this.sec = sec
}
}
const v = new VideoLesson('Таймеры', 900)
v instanceof VideoLesson // true
v instanceof Lesson // true
v instanceof Object // true
v instanceof Array // false
Выражение obj instanceof C ищет C.prototype в цепочке [[Prototype]] объекта obj.
С функциями-конструкторами #
До class проверка та же:
function Tag(name) {
this.name = name
}
const t = new Tag('javascript')
t instanceof Tag // true
t instanceof Object // true
instanceof привязан к функции/классу, с которым связывали прототип при создании, а не к форме литерала объекта.
Ограничения instanceof #
Проверка идёт по цепочке прототипов, не по «форме» данных:
const plain = { title: 'Plain', complete() {} }
plain instanceof Object // true
const lesson = new Lesson('OOP')
plain instanceof Lesson // false
Объекты из другого окна iframe или Realm могут дать ложный false для того же класса — разные глобальные конструкторы.
Для плоских структур данных чаще проверяют поля вручную или используют TypeScript на этапе сборки, а не instanceof.
Symbol.hasInstance #
Класс может переопределить поведение instanceof:
class Box {
static [Symbol.hasInstance](value) {
return (
typeof value === 'object' &&
value !== null &&
'width' in value &&
'height' in value
)
}
}
const slot = { width: 100, height: 50 }
slot instanceof Box // true
Используйте редко — усложняет чтение кода; уместно для библиотечных абстракций с нестандартными экземплярами.
Примеси: идея #
JavaScript допускает одно наследование через extends. Примесь копирует методы в прототип или в класс, чтобы «подмешать» возможности:
const Timestamped = {
touch() {
this.updatedAt = Date.now()
return this
},
}
const Taggable = {
addTag(tag) {
if (!this.tags) this.tags = []
this.tags.push(tag)
return this
},
}
function applyMixins(target, ...sources) {
for (const source of sources) {
Object.assign(target.prototype, source)
}
}
class Note {
constructor(text) {
this.text = text
this.createdAt = Date.now()
}
}
applyMixins(Note, Timestamped, Taggable)
const n = new Note('повторить замыкания')
n.addTag('homework').touch()
n.tags // ['homework']
n.updatedAt // число, мс
Это композиция поведения без цепочки extends на каждый чих.
Object.assign и классы #
Для современных классов миксины часто оформляют как функции от базового класса:
function Timestamped(Base) {
return class extends Base {
touch() {
this.updatedAt = Date.now()
return this
}
}
}
class Article {
constructor(title) {
this.title = title
}
}
const TimestampedArticle = Timestamped(Article)
const a = new TimestampedArticle('Прототипы')
a.touch()
a instanceof Article // true
Паттерн «миксин как функция от суперкласса» лучше сочетается с extends и super, чем слепое копирование в prototype.
instanceof vs typeof #
typeof | instanceof | |
|---|---|---|
| примитивы | да | нет (не объект) |
| обычные объекты | "object" | нужен конструктор |
| массивы | "object" | Array лучше через Array.isArray |
| классы | не различает | да, по прототипу |
typeof 'jsflow' // "string"
'jsflow' instanceof String // false (примитив)
typeof [] // "object"
[] instanceof Array // true
Array.isArray([]) // true — предпочтительно для массивов
Частые ошибки #
instanceofдля примитивов — всегдаfalse, кроме обёртокnew String().- Мутация чужого prototype через миксин — коллизии имён методов у разных примесей; проверяйте пересечения.
- Смешение duck typing и instanceof — объект с теми же полями не пройдёт
instanceof Lesson. - Проверка плоского JSON —
instanceofбессмысленен послеJSON.parse; там только plain objects.
Итог #
instanceofсмотрит на цепочку прототипов, а не на сходство полей.- Наследование
extendsдаёт естественныйtrueдля предков класса. - Примеси добавляют общие методы нескольким типам без глубокого дерева наследования.
- Для массивов —
Array.isArray; для примитивов —typeof; для доменных объектов — классы и осознанныйinstanceof.