❓👨‍💻 Вопросы для подготовки к собеседованию по JavaScript. Часть 2

Продолжаем разбирать вопросы для джунов: рассказываем о прототипном наследовании, цикле событий, методах сохранения данных в браузере, конструкторах, генераторах, функциональных выражениях, микро- и макрозадачах.

21. Что такое DOM?

Что такое DOM?

DOM (Document Object Model) – это модель, которая представляет HTML-документ в виде дерева тегов. Каждый HTML-тег в этом дереве является объектом. Вложенные теги являются дочерними элементами по отношению к своему родительскому элементу. Текст внутри тега также является объектом. Все эти объекты доступны для любых манипуляций с помощью JavaScript, а эти манипуляции, в свою очередь, позволяют динамически управлять содержимым страницы. В приведенном ниже примере мы выбираем элемент с классом "story" и сохраняем его в переменной story. Затем мы выбираем элементы с id "set-text" и "clear-text" и добавляем им обработчики событий. Когда на элемент с id "set-text" нажимают, текстовое содержимое элемента с классом "story" меняется на "Ночь 31 декабря началась снегопадом и метелью." Когда нажимают на элемент с id "clear-text", текстовое содержимое элемента с классом "story" очищается:

<!DOCTYPE html>
<html>
<head>
   <title>Пример манипуляции DOM</title>
</head>
<body>
   <div class="story">Это текст новогодней истории.</div>
   <button id="set-text">Изменить div-элемент story</button>
   <button id="clear-text">Очистить div-элемент</button>

   <script>
       const story = document.body.querySelector(".story");

       const setText = document.body.querySelector("#set-text");
       setText.addEventListener("click", () => {
           story.textContent = "Ночь 31 декабря началась снегопадом и метелью.";
       });

       const clearText = document.body.querySelector("#clear-text");
       clearText.addEventListener("click", () => {
           story.textContent = "";
       });
   </script>
</body>
</html>

22. Что такое цикл событий?

Что такое цикл событий в JavaScript?

Цикл событий в JavaScript – это механизм, который управляет выполнением кода. Он обеспечивает обработку событий и выполнение задач в правильном порядке. Хотя JavaScript работает в однопоточной среде, цикл событий дает возможность обрабатывать асинхронные операции и предотвращает блокировку основного потока выполнения.

Когда асинхронная операция (например, запрос к серверу) завершается, она помещает соответствующее событие в очередь событий. Цикл событий обрабатывает задачи в порядке их поступления. Он берет событие из очереди и передает его для выполнения. Если событие содержит обратный вызов или обработчик – вызывается соответствующая функция для выполнения кода, связанного с этим событием. Цикл событий также обрабатывает задачи, связанные с таймерами и промисами. Благодаря циклу событий, JavaScript быстро реагирует на действия пользователя и эффективно использует ресурсы при работе с асинхронными операциями .

В приведенном ниже примере, несмотря на то, что setTimeout(firstTask, 0) имеет время ожидания 0 секунд, первая задача firstTask() все равно не будет выполнена сразу после secondTask(). Это происходит потому, что JavaScript использует цикл событий для управления выполнением асинхронных операций. Когда мы вызываем setTimeout(), функция firstTask() помещается в очередь событий, – цикл событий начинает обрабатывать эту функцию, когда стек вызовов опустеет.

	function firstTask() {
 // Эмуляция долгой операции
 let i = 1000000000;
 while (i > 0) {
   i--;
 }
 console.log('Первая задача выполнена');
}

function secondTask() {
 console.log('Вторая задача выполнена');
}

setTimeout(firstTask, 0);
secondTask();
Больше полезных материалов вы найдете на нашем телеграм-канале «Библиотека фронтендера»

23. Что такое прототипное наследование?

Что такое прототипное наследование в JavaScript?

Прототипное наследование в JavaScript – это механизм, который позволяет одному объекту наследовать свойства и методы другого объекта. Это основной способ наследования в JavaScript.

Каждый объект в JavaScript имеет внутреннее скрытое свойство prototype, которое ссылается на другой объект. Этот другой объект называется прототипом первого объекта. При попытке получить доступ к свойству объекта, JavaScript сначала проверяет, есть ли это свойство в самом объекте. Если нет, он ищет его в прототипе объекта. Если и там его нет, то в прототипе прототипа и так далее. Если свойство или метод отсутствуют в объекте и его прототипе, JavaScript вернет undefined:

// Прототип TV
let TV = {
 brand: 'Generic',
 smart: true,
 resolution: '4k',
 turnOn() {
   console.log(`Включаем ${this.brand}`);
 },
 turnOff() {
   console.log(`Выключаем ${this.brand}`);
 }
};

// Создаем экземпляр Samsung TV
let samsungTV = {
 __proto__: TV,
 brand: 'Samsung'
};

// Создаем экземпляр Sony TV
let sonyTV = {
 __proto__: TV,
 brand: 'Sony'
};

// Используем существующие методы экземпляров
samsungTV.turnOn(); // Включаем Samsung
sonyTV.turnOff(); // Выключаем Sony

// Вызываем существующее свойство
console.log(sonyTV.smart); // true

// Вызываем несуществующее свойство
console.log(samsungTV.size); // undefined

24. Для чего нужен оператор опциональной последовательности?

Для чего нужен оператор опциональной последовательности в JavaScript?

Оператор опциональной последовательности ?. позволяет получить безопасный доступ к вложенным свойствам объекта – даже в том случае, когда промежуточное свойство отсутствует. Оператор ?. прекращает оценку и возвращает undefined, если часть после ?. является либо undefined, либо null.

let user = {
 name: 'Евгений',
 lastname: 'Онегин',
 address: {
   street: 'Невский проспект',
   city: 'Санкт-Петербург'
 }
};

console.log(user.address?.street); // Невский проспект
console.log(user?.lastname); // Oнегин
console.log(user?.phone?.number); // undefined - нет ошибки
console.log(user.phone.number); // Uncaught TypeError: Cannot read properties of undefined (reading 'number')

25. Что такое теневой DOM?

Что такое теневой DOM?

Shadow DOM – это техника, позволяющая создавать изолированные фрагменты HTML и CSS в специальном сегменте DOM, который находится внутри определенного элемента. Такой подход позволяет исключить влияние инкапсулированных стилей на структуру и внешний вид основной страницы, и обеспечивает большую гибкость и контроль над представлением и поведением элементов.

В приведенном ниже примере инкапсулированные стили пользовательского элемента не влияют на внешний вид остальной части страницы:

<p>Привет, мир!</p>
<!-- Пользовательский элемент -->
<my-element></my-element>

<script>
class MyElement extends HTMLElement {
 constructor() {
   super();
   // Создаем Shadow DOM
   let shadow = this.attachShadow({mode: 'open'});
   // Добавляем в Shadow DOM
   shadow.innerHTML = `
     <style> p { color: #fff; background-color: #000; font-family: Consolas ; font-size: 25px; } </style>
     <p>Привет, мир!</p>
   `;
 }
}
// Регистрируем пользовательский элемент
customElements.define('my-element', MyElement);
</script>
Shadow DOM инкапсулирует стили и структуру

26. Что такое рекурсия и как ее можно использовать в JavaScript?

Что такое рекурсия и как ее можно использовать в JavaScript?

Рекурсия в программировании – это процесс, в котором функция вызывает саму себя. Рекурсия обычно используется для решения задач, которые можно разбить на более простые подзадачи. В JavaScript рекурсию можно использовать, например, для работы с многоуровневыми массивами и обхода деревовидных структур данных.

Так можно преобразовать вложенный массив в одномерный:

const flattenArray = arr => arr.reduce((accumulator, value) => accumulator.concat(
 Array.isArray(value) ? flattenArray(value) : value
), []);

let result = flattenArray([1, [2, [3, [4, 5], [6, 7, 8, [9, 10]]]]]);
console.log(result); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

А так можно обойти и визуализировать дерево:

<!DOCTYPE html>
<html>
<body>
<pre id="result"></pre>

<script>
let tree = {
 value: '-root',
 children: [
 {
 value: '|child1',
 children: [
   { value: '-|grandchild1' },
   { value: '-|grandchild2' }
 ]
 },
 {
 value: '|child2',
 children: [
   { value: '-|grandchild3' },
   {
     value: '-|grandchild4',
     children: [
       { value: '--|great-grandchild1' },
       { value: '--|great-grandchild2' }
     ]
   }
 ]
 }
 ]
};

function traverseTree(node, level = 0) {
 let indent = ' '.repeat(level * 2);
 let resultDiv = document.getElementById('result');
 resultDiv.innerHTML += `${indent}${node.value}\n`;
 if (node.children) {
 node.children.forEach(childNode => traverseTree(childNode, level + 1));
 }
}

traverseTree(tree);
</script>
</body>
</html>

Результат:

27. В чем разница между объявлением функции и функциональным выражением?

Объявление функции и функциональное выражение – это два способа определения функций в JavaScript.

Объявление функции – это традиционный способ определения функции. Функция создается и присваивается переменной, как любое другое значение. При этом объявленные функции доступны во всем коде, даже до того, как программа достигает того участка, где они определены:

console.log(typeof myFunction); // Вывод: function

function myFunction() {
 console.log('Привет, админ!');
}

Функциональное выражение – это альтернативный способ определения функции:

let greet = function() {
 console.log('Привет, админ!');
}

greet(); // Привет, админ!

В отличие от традиционной функции, функциональное выражение нельзя вызывать до определения в коде – это приведет к ошибке . Функциональные выражения могут:

  • быть анонимными;
  • формировать замыкания;
  • передаваться в качестве аргументов другим функциям;
  • использоваться как немедленно вызываемые функциональные выражения (IIFE).

28.Что такое функции-конструкторы?

Что такое функции-конструкторы в JavaScript ?

Конструкторы в JavaScript – это специальные функции, используемые для создания объектов. Вот два основных правила при работе с конструкторами:

  • Имя конструктора должно начинаться с заглавной буквы.
  • Конструктор вызывается при помощи оператора new.

Когда мы вызываем конструктор через new, происходит следующее:

  • Создается новый пустой объект и присваивается в this.
  • Выполняется код внутри конструктора. Обычно он модифицирует объект this, добавляя в него свойства.
  • Значение this возвращается из конструктора как результат.

Например:

function User(name) {
  this.name = name;
  this.sayHi = function() {
    alert(`Привет, меня зовут ${this.name}!`);
  };  
}

let user = new User("Вася"); 
user.sayHi(); // Привет, меня зовут Вася!

Здесь User – функция-конструктор. Когда мы вызываем конструктор через new User("Вася"), создается объект user с указанным именем и методом sayHi. Таким образом с помощью конструкторов можно многократного создавать объекты по одному шаблону.

29. Как получить список ключей и значений объекта?

Как получить список ключей и значений объекта в JavaScript?

В JavaScript для получения списка ключей и значений объекта используются методы Object.keys() и Object.values().

Object.keys() возвращает массив со всеми ключами объекта:

const person = {
  name: "Егор",
  age: 30,
  city: "Ростов"
};

const keys = Object.keys(person);
console.log(keys); // ['name', 'age', 'city']

Object.values() возвращает массив со всеми значениями свойств объекта:

const person = {
  name: "Егор",
  age: 30,
  city: "Ростов"
};

const values = Object.values(person);
console.log(values); // ['Егор', 30, 'Ростов']

Используя Object.keys() и Object.values(), можно сделать перебор:

const person = {
  name: "Егор",
  age: 30,
  city: "Ростов"
};

Object.keys(person).forEach(key => {
  console.log(key, person[key]);  
});

Или вывести ключи и значения в виде объекта:

const person = {
  name: "Егор",
  age: 30,
  city: "Ростов"
};

Object.entries(person).forEach(([key, value]) => {
  console.log(`${key}: ${value}`); 
});

30. Приведите примеры нововведений, добавленных в JavaScript в версии ES6

Приведите примеры нововведений, добавленных в JavaScript в версии ES6

В ES6 множество нововведений, вот всего несколько примеров.

Деструктуризация объектов и массивов:

// Деструктуризация объекта
const user = {
  name: "Алиса",
  age: 20 
}

const {name, age} = user; 
// name = "Алиса"; age = 20

// Деструктуризация массива
const array = [1, 2, 3];
let [x, y] = array;
// x = 1; y = 2

Шаблонные строки:

const name = "Инна";
console.log(`Привет, ${name}!`); // Привет, Инна!

Стрелочные функции:

const sum = (a, b) => a + b;

console.log(sum(1, 2)); // 3

Параметры по умолчанию:

function volume(x = 1, y = 2, z = 3) {
  return x * y * z;
}

volume(); // 6
volume(2); // 12

Объявление переменных с помощью let и const.

let – аналог var, но с блочной областью видимости:

{
  let x = 1;
}
console.log(x); // Ошибка, x не видна за пределами блока

const – объявление константы (переменной, которую нельзя изменить):

const PI = 3.14; 

PI = 2; // Ошибка, нельзя изменить значение

Оператор распространения (позволяет распаковывать элементы массива или объекта для аргументов функции или создания новых массивов/объектов):

const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };
const merged = { ...obj1, ...obj2 }; 
console.log(merged); // { a: 1, b: 2, c: 3, d: 4 }

31. Как происходит наследование классов в ES6?

Наследование классов в ES6 осуществляется с помощью ключевого слова extends, которое следует за именем родительского класса. Родительский класс часто называют базовым классом, а класс, который наследует базовый/родительский класс, называется производным или дочерним:

class Parent {
 constructor(name) {
   this.name = name;
 }

 greeting() {
   return `Добрый день, ${this.name}!`;
 }
}

class Child extends Parent {
 constructor(name, age) {
   super(name); // вызывает конструктор родительского класса
   this.age = age;
 }

 greeting() {
   let greeting = super.greeting(); // вызывает метод родительского класса
   return `${greeting} Как поживаешь?`;
 }
}

let vasya = new Child('Вася', 10);
console.log(vasya.greeting()); // Добрый день, Вася! Как поживаешь?

32. Что такое микрозадачи и макрозадачи?

Что такое микрозадачи и макрозадачи в JavaScript

В JavaScript микрозадачи и макрозадачи относятся к типам задач, которые должны выполняться в цикле событий.

Микрозадачи – это задачи, которые должны быть выполнены в текущем цикле событий перед тем, как браузер перерисует страницу. Они обычно добавляются в очередь выполнения с помощью методов, таких как Promise.then(), process.nextTick() (в Node.js) или MutationObserver. Примеры микрозадач – выполнение обработчиков промисов и мутации DOM.

С другой стороны, макрозадачи – это задачи, которые должны быть выполнены после окончания текущего цикла событий и перед тем, как изменения будут отрендерены на экране. Это включает задачи, добавленные в очередь событий с помощью setTimeout, setInterval, requestAnimationFrame, а также обработку входных событий и сетевых запросов. Макрозадачи выполняются после того, как завершается обработка всех микрозадач в текущем цикле событий.

Разница между микрозадачами и макрозадачами определяет порядок выполнения и позволяет управлять приоритетами различных задач в JavaScript. Микрозадачи имеют более высокий приоритет и выполняются до макрозадач, что позволяет быстрее обновлять интерфейс и предотвращает блокировку основного потока выполнения JavaScript. В приведенном ниже примере setTimeout является макрозадачей, а Promise.then() – микрозадачей. Поскольку микрозадачи имеют более высокий приоритет, Promise.then() выполняется перед setTimeout, и поэтому promise появляется в первую очередь:

setTimeout(() => alert("я - макрозадача"));

Promise.resolve()
 .then(() => alert("я - микрозадача"));

alert("я - синхронная операция основного кода");

33. Что такое генераторы?

Что такое генераторы в JavaScript?

Генераторы в JavaScript представляют собой специальный тип функций, которые генерируют последовательность значений по одному за раз по мере необходимости, и позволяют приостанавливать и возобновлять свое выполнение (в отличие от обычных функций, которые выполняются до завершения). Генераторы хорошо работают с объектами и упрощают создание потоков данных.

Чтобы объявить генератор, используют специальный синтаксис – функцию-генератор. Функция-генератор определяется с помощью символа * после ключевого слова function:

function* generateNumbers() {
 yield 1;
 yield 2;
 yield 3;
 yield 4;
 return 5;
}

let gen = generateNumbers();

console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: 4, done: false }
console.log(gen.next()); // { value: 5, done: true }

Генератор возвращает итератор, который можно использовать для контроля над выполнением функции. Основной метод итератора – next(). Когда вызывается next(), выполнение кода продолжается до ближайшего оператора yield. Когда достигнут yield, выполнение функции приостанавливается, и соответствующее значение возвращается во внешний код.

34. Какие существуют методы для сохранения данных в браузере?

Какие существуют методы для сохранения данных в браузере?

Есть 3 основных метода хранения данных в браузере:

  • LocalStorage и SessionStorage используются для хранения пар ключ-значение. Данные, сохраненные в них, сохраняются после обновления страницы. При этом только LocalStorage может сохранять данные после перезапуска браузера. Оба хранилища могут использовать только строки в качестве ключей и значений, поэтому объекты необходимо преобразовать с помощью JSON.stringify().
  • Cookie – небольшие строки данных, которые хранятся в браузере. Cookie обычно устанавливаются веб-сервером с использованием заголовка Set-Cookie. Браузер затем автоматически добавляет их почти ко всем запросам на тот же домен с использованием заголовка Cookie. Один экземпляр cookie может содержать до 4 кб данных. В зависимости от браузера, допускается более 20 cookie на сайт.
  • IndexedDB – встроенная база данных, более мощная, чем localStorage. Это NoSQL-хранилище данных в формате JSON внутри браузера, где доступны несколько типов ключей, а значения могут быть практически любым. IndexedDB поддерживает асинхронный доступ, транзакции для обеспечения согласованности данных и создание индексов для эффективного поиска. Позволяет хранить больше данных, чем localStorage, может быть связана с Service Workers и другими технологиями, которые обеспечивают функционирование PWA в оффлайне.

Вот примеры использования каждого из этих методов хранения данных:

// LocalStorage
localStorage.setItem('фрукт', 'апельсин');
console.log(localStorage.getItem('фрукт')); // Выводит: апельсин

// SessionStorage
sessionStorage.setItem('тема', 'dark');
console.log(sessionStorage.getItem('тема')); // Выводит: dark

// Cookie
document.cookie = "username=Вася Пупкин; SameSite=None; Secure";
console.log(document.cookie); // Выводит: username=Вася Пупкин

// IndexedDB
let indexedDB = 
  window.indexedDB || 
  window.mozIndexedDB ||
  window.webkitIndexedDB || 
  window.msIndexedDB;

if (!indexedDB) {
  console.log("IndexedDB не поддерживается");
} else {

  let open = indexedDB.open("myDatabase");

  open.onupgradeneeded = function() {
    let db = open.result;
    db.createObjectStore("notes", {keyPath: "id"});
  };

  open.onsuccess = function() {
    console.log("База данных создана");
  };

  open.onerror = function() {
    console.log("Ошибка!");
  };

}

35. В чем заключается разница между sessionStorage и localStorage?

Сессионное хранилище sessionStorage и локальное хранилище localStorage позволяют сохранять данные в формате ключ-значение в браузере. Оба они используются для хранения данных на стороне клиента, но имеют некоторые отличия:

  • Объем хранимых данных – localStorage может хранить до 10 МБ данных, в то время как sessionStorage может хранить только до 5 МБ данных.
  • Срок хранения данных – в localStorage данные не удаляются, когда закрывается браузер или вкладка. И напротив, данные в sessionStorage удаляются, когда закрывается вкладка или окно браузера.
  • Доступность данных – из localStorage данные доступны в любом окне браузера, в то время как данные из sessionStorage доступны только из того же окна браузера, где они были сохранены.

Пример использования localStorage:

// Установка значения
localStorage.setItem('жанр', 'триллер');

// Получение значения
let value = localStorage.getItem('жанр');

console.log(value);

// Удаление значения
localStorage.removeItem('жанр');

// Очистка всего хранилища
localStorage.clear();

Пример использования sessionStorage:

// Установка значения
sessionStorage.setItem('стиль', 'индастриал');

// Получение значения
let value = sessionStorage.getItem('стиль');

console.log(value);

// Удаление значения
sessionStorage.removeItem('стиль');

// Очистка всего хранилища
sessionStorage.clear();

36. Что такое регулярные выражения?

Что такое регулярные выражения?

Регулярные выражения (regex) – это паттерны, которые используются для поиска и замены текста в строках. В паттернах используются комбинации специальных символов (шаблоны) и флаги (модификаторы, которые определяют поведение поиска). Регулярные выражения часто используют в комбинации с этими методами:

  • test() – проверка на соответствие шаблону
  • match() – поиск соответствий
  • replace() – замена по шаблону
  • search() – поиск индекса

Составлять регулярные выражения можно с помощью конструктора RegExp или литеральной нотации.

Пример определения email-адреса в строке с помощью конструктора RegExp:

const emailRegex = new RegExp('[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}', 'gi');

const email = 'Присылайте резюме на test@example.com, отвечу всем'; 

console.log(emailRegex.test(email)); // true

Пример извлечения телефонного номера с помощью литеральной нотации:

const phone = 'Теперь у меня новый номер +79876543210, на связи с 09:00 до 21:00';

const phoneRegex = /\+7\d{10}/gi;

const found = phone.match(phoneRegex);

if (found) {
  console.log(found[0]); 
} else {
  console.log('Совпадения не найдены'); 
}

37. В чем заключается разница между WeakSet, WeakMap и обычными Set и Map?

WeakSet и WeakMap – это специальные структуры данных в JavaScript, которые отличаются особенностью хранения ссылок на объекты.

В обычных Set и Map хранятся сильные ссылки на объекты. Это значит, что пока существует ссылка на объект в этих структурах, сборщик мусора не удалит этот объект из памяти, даже если больше нигде в коде нет ссылок на него.

И напротив, в WeakSet и WeakMap хранятся слабые ссылки. Это означает, что если объект, на который есть ссылка в этих структурах, больше недоступен в коде (т.е. нигде больше нет сильных ссылок на него), то сборщик мусора может удалить этот объект из памяти, даже если в WeakSet или WeakMap все еще есть ссылка на него. Таким образом, использование слабых ссылок позволяет не держать в памяти ненужные больше объекты и экономить память.

Кроме того, в WeakMap в качестве ключей могут использоваться только объекты, а не примитивные значения. А в WeakSet хранятся только объекты, без ключей.

38. Почему два объекта с одинаковыми полями возвращают false при сравнении?

В JavaScript объекты сравниваются по ссылкам на область памяти, где они хранятся. Два разных объекта, даже если у них одинаковые поля и значения этих полей, располагаются в разных областях памяти. Например, у нас есть:

const example1 = {fruit: 'яблоко'};
const example2 = {fruit: 'яблоко'};
console.log(example1 == example2); // false

Хотя у этих двух объектов одно и то же поле fruit со значением 'яблоко', на самом деле это два абсолютно разных объекта, которые хранятся в разных ячейках памяти. Поэтому если мы сравним их, результат будет false. Для того чтобы два объекта считались равными, нужно, чтобы это был один и тот же объект, то есть чтобы обе переменные ссылались на одну и ту же область памяти.

39. Как в JavaScript реализованы методы примитивных типов данных?

JavaScript позволяет работать с примитивными типами данных – строками, числами, логическими значениями – как с объектами, поскольку у них тоже есть методы. Например, у строк есть методы toUpperCase() и toLowerCase(), у чисел есть методы toFixed() и toPrecision() и т.д.

Эта возможность реализована благодаря специальным оберточным объектам для каждого примитивного типа данных. Эти объекты называются:

  • String – для строк
  • Number – для чисел
  • Boolean – для логических значений
  • Symbol – для символов

Когда мы вызываем метод у примитивного значения, например "test".toUpperCase(), происходит следующее:

  • Создается временный оберточный объект типа String со значением "test".
  • У этого объекта вызывается метод toUpperCase().
  • Результат возвращается обратно в примитивное значение.
  • Временный оберточный объект удаляется.

Таким образом реализуется возможность использовать методы у примитивных типов данных. Благодаря этому механизму, примитивы в JavaScript ведут себя как объекты.

40. Как проверить, из какого класса был создан объект?

Как проверить, из какого класса был создан объект?

Для этого в JavaScript используется оператор instanceof. Он позволяет проверить, из какого класса был создан объект, учитывая наследование.

Например, есть базовый класс Animal и классы-наследники Dog и Cat:

class Animal {
  constructor() {}
}

class Dog extends Animal {
  constructor() {
    super(); 
  }
  
  bark() {}
}

class Cat extends Animal {
  constructor() {
    super();
  }
  
  meow() {} 
}

Создадим объект класса Dog и проверим с помощью оператора instanceof, является ли объект экземпляром указанного класса или классов-родителей:

const dog = new Dog();

console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
console.log(dog instanceof Cat); // false

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ