3 принципа ООП - это миф, притом очень вредный.
Инкапсуляция, наследование и полиморфизм относятся к ООП примерно как кухонный комбайн к варке блюд, их конечно можно использовать вместе, но и друг без друга они живут прекрасно.
ООП - это про высокоуровневые абстракции представленные в виде объектов, а так же про взаимодействие этих объектов, путем вызова методов. Все. Никаких 3 принципов тут нет. И это примерно одинаково, что в JS, что в C#, что в Java, что в C++, а разница в реализации тут не существенна, Вы о ней знаете, только потому что
абстракции текут.
Инкапсуляция - это сокрытие сложности. Опять же все. Забудьте весь бред про приватные поля и тому подобное. Это не более чем частный случай. И замыкания - это тоже частный случай инкапсуляции. А еще, когда из модуля Вы экспортируете не все подряд (привет ReasonML), а только высокоуровневые штуки, оставляя все остальное недоступным извне. А еще когда некоторое API (REST/GraphQL/RPC/etc.) отгораживает Вас от прямого общения с базой данных - это тоже инкапсуляция.
Полиморфизм - это возможность некоторой сущности работать с разными типами данных и возможно адаптироваться под них. А еще полиморфизм разный бывает. Помимо полиморфизма подтипов, который чаще всего и приписывают к ООП, бывают и другие. Например очень распространен параметрический полиморфизм, в большинстве языков представленный дженериками. А еще бывает ad-hoc полиморфизм (перегрузки).
Но даже если рассматривать только полиморфизм подтипов (это когда между типами есть иерархия, то есть отношения подтип-надтип, и мы в переменную/аргумент/поле надтипа можем присвоить значение подтипа), то и тут нет не слова про ООП. Яркий пример - TypeScript, где есть тип unknown, являющийся надтипом любого другого типа и в который можно присвоить значение любого типа, а так же есть тип never, являющийся подтипом любого другого типа и который можно присвоить в любой другой тип.
Ну и касательно JS, в нем вообще все полиморфно, ибо динамическая типизация.
Наследование - это вообще частный случай полиморфизма подтипов, отдельно выделяют пожалуй лишь потому, что он не только про типы (как полиморфизм), а еще и про реализации, которые наследуются. И опять же, ничего тут про ООП нет. Наследование вполне может жить и без ООП,
например во вполне себе структурно-процедурном Си#include <stdio.h>
struct Parent
{
int a;
int b;
};
struct Child
{
struct Parent __super;
int c;
};
void f(struct Parent* v)
{
printf("a: %d\nb: %d\n", v->a, v->b);
}
int main()
{
struct Child v = {{10, 20}, 30};
f((struct Parent*)&v);
printf("c: %d\n", v.c);
return 0;
}
P.S. JS вполне себе компилируемый язык, у v8 даже ассемблерный код попросить можно, который он накомпилит с Вашего JS.