Раз і назавжди про `this` в JS людською мовою
Питання про this — одне з улюблених питань багатьох інтерв’юверів.
Причому люблять його не лише токсичні скуфи хто глядять слабкі місця. Але й емпатичні зумери.
Скуфи люблять це питання, бо багато людей замість запам’ятати суть зубрять задачки і зазубрють неправильно, або й взагалі забивають і потім путаються. Це одне з питань яке «бий, не промахнешся».
А я люблю це питання, бо воно не про синтаксис і не про зубріння задачок, але про розуміння принципу. Особливо класно питати про this у зв’язці із замиканнями (наприклад приклад 4.3 в кінці). Це забирає мало часу, один простий приклад, але хід думок людини зразу ж показує чи вона просто «відчуває», чи розуміє механізм.
І власне я Андрій (linkedin), і сьогодні коротенько, але людською мовою розкажу не лише про як поводиться this, але й чому він так робить і звідки береться. Поясню сам механізм і поділюсь простою схемою як розв’язати будь-яку задачу на this.
Спершу коротко
Якщо коротко, то this — це неявний параметр функції. Впринципі все :)
Якщо ж ні, то почнемо трошечки здалеку:
Що таке функція в javascript?
Я вже писав про це в попередній статті, там ця думка розкрита детальніше, але ці теми тісно пов’язані і тому мушу хоча б частково повторитись і тут:
Функція в js — це об’єкт. Не простий об’єкт, а callable (викликабельний, викликовуваний, такий, що може бути викликаним), і тим не менш — об’єкт.
Функція в js сама по собі нічого не робить.
Функція в js просто береже набір інструкцій, свій тип і інші дані.
Фактично коли ми пишемо function logSomething(smth) {console.log(smth)}, рушій розуміє це як «створи в пам’яті об’єкт з назвою logSomething і збережи в ньому наступні значення:»
{
"[[ECMAScriptCode]]": "console.log(smth)", // сам код, який треба виконати
"[[FormalParameters]]": ["smth"], // список параметрів, які треба очікувати зовні
"[[Strict]]": false, // в якому режимі запускати код функції? true якщо в строгому
"[[FunctionKind]]": "normal", // якби ми оголосили її як стрілкову функцію, то було б "arrow",
"[[Envrionment]]": // сюди зберігається посилання на лексичне середовище місця, де функцію було оголошено. Від цього дуже залежить як працюватиме замикання і про це є детально розібрано в попередній статті.
// ... деякі інші значення, які сьогодні розглядати не будемо
}
А потім, коли ми пишемо в іншому місці logSomething(67), рушій розуміє це так: Запусти код, який записаний в logSomething.[[ECMAScriptCode]] і коли той код буде питати яке значення параметра з назвою smth — треба надати йому 67)«.
this — це теж параметр, просто implicit
В прикладі вище ми маємо explicit (явний, явно визначений) параметр smth. Явно визначений, бо ми самі явно визначили, ми самі явно його прописали. Якби ми його явно не прописали, то його б не існувало.
this — це теж параметр. Це не просто ключове слово яким заборонено називати змінні. this — це параметр. Просто неявний. Ми не мусимо і не можемо його оголосити коли створюємо функцію (ми не можемо написати function doSmth(this) {}), але навіть без нашого явного оголошення рушій js створює той параметр сам.
Коли ми пишемо logSomething(34) — ми явно вказуємо рушію що використати як значення smth під час виконання коду, який збережений в logSomething.
А значення неявного параметра this рушій визначає сам відповідно до своїх правил.
За якими правилами рушій js визначає що використати в якості значення this?
Рушій js вирішує що покласти в this ПО ЧЕРЗІ згідно 4 критеріїв:
Який тип функції? Вона стрілкова чи нормальна? (яке значення [[FunctionKind]]?)
Чи є «пришите» значення this з допомогою .bind()/.call()/.apply()?
Де саме було викликано? Функцію було викликано як метод об’єкта чи як конструктор чи як самостійну змінну?
Код функції має бути запущений в строгому режимі чи ні? (яке значення [[Strict]]?)
Власне ці 4 кроки, ці 4 критерії — це все, що треба запам’ятати. Все інше виводиться з них як похідне.
Тип функції
Якщо функція стрілкова, то для неї рушій взагалі не створює this.
Для стрілкової функції рушій глядить значення this так само, як і глядить значення звичайної змінної в звичайній ситуації.
Приклад:
const variableOutsideArrowFunction = 34;
console.log(variableOutsideArrowFunction);
console.log(this);
const arrowFunction = () => {
console.log(variableOutsideArrowFunction); // те ж саме, що на рядку 3
console.log(this); // те ж саме, що на рядку 4
};
arrowFunction();
Функція arrowFunction не має параметра з назвою variableOutsideArrowFunction. Тому коли рушій запускає код функції arrowFunction і доходить до місця, де треба використати значення variableOutsideArrowFunction, рушій глядить значення variableOutsideArrowFunction за межами arrowFunction. Аналогічно він робить і з this.
Якщо функція стрілкова, то рушій не буде дивитись на інші критерії. Якщо функція стрілкова, то рушій не буде для неї створювати this впринципі. Рушій просто буде глядіти this за межами стрілкової функції за тим же принципом, як він глядить звичайні змінні.
Але якщо функція не стрілкова, то рушій зверне увагу на наступний критерій:
Чи є пришите значення з допомогою .bind()?
Вище я казав, що ми не можемо явно вказати рушію що саме використовувати в функції в якості this. І це справді так, під час оголошення функції не можемо.
Але уже після того як ми оголосили функцію, після того ми можемо викликати її з допомогою .call() або .apply() і тоді вказати рушію що використовувати в якості this явно.
Також ми можемо взяти нашу уже оголошену функцію і з допомогою .bind() створити її «генно-модифіковану» версію в яку явно вписати рушію що саме використовувати в якості this.
Тому якщо це не стрілкова функція, а звичайна, і ми явно вказали рушію що використати в якості this, то саме то рушій і використає в якості this.
Якщо ж ми явно не вказали, то рушій дивиться на наступний критерій:
Як було викликано
Якщо функція не стрілкова і ми не «пришили» їй this явно, то:
Якщо функція була викликана як конструктор (після слова new), то значенням this рушій передасть той об’єкт, який функція «конструюює».
Якщо функція була викликана як метод об’єкта, то значенням this рушій передасть той об’єкт, в якості метода якого функцію було викликано.
Якщо ж функція була викликана як звичайна функція, то рушій зверне увагу на останній критерій:
Строгий чи звичайний режим
Якщо функція не стрілкова. І функція була викликана не як метод об’єкта і не як конструктор, а як звичайна функція. То
Якщо код функції має виконуватись в строгому режимі, то значенням явного параметра this рушій призначить undefined.
Якщо код функції має виконуватись в не строгому режимі, то в якості this рушій призначить глобальний об’єкт (window в браузері, global в node.js).
Все
Далі я розберу багато прикладів для закріплення. Але нового нічого не розкажу.
Сама суть уже описана вище. Все.
Приклади
Окрема функція в строгому і не строгому режимах
Приклад 1.1.
const func1 = function () {
console.log(this);
};
function func2() {
console.log(this);
}
func1();
func2();
Приклад 1.2.
function wrapper() {
function func() {
console.log(this);
}
func();
}
wrapper();
Приклад 1.3.
"use strict";
const func1 = function () {
console.log(this);
};
function func2() {
console.log(this);
}
func1();
func2();
Приклад 1.4.
const sloppyFunction = function () {
console.log(this);
};
const strictFunction = function () {
"use strict";
sloppyFunction();
console.log(this);
};
strictFunction();
Пояснення прикладу 1.1. В обох випадках ми маємо звичайну, а не стрілкову функцію. В обох випадках вони не мають «пришитого» значення this. В обох випадках ті функції викликані як звичайні функції, а не як конструктори чи методи об’єкта. І в обох випадках функції створились в sloppy mode. Тому в обох випадках в консолі буде window.
Пояснення прикладу 1.2. Ми маємо звичайну функцію func, не стрілкову. Ані bind, ані call/apply використано не було. Вона викликана як звичайна функція, не як метод чи конструктор. І в нестрогому режимі. Тому в консолі буде window.
Пояснення прикладу 1.3. В обох випадках ми маємо звичайну, а не стрілкову функцію. В обох випадках значення this явним способом не прив’язане. В обох випадках ті функції викликані як звичайні функції, а не як конструктори чи методи об’єкта. Але на відміну від попередніх прикладів в обох випадках функції створились в strict mode. Тому в обох випадках в консолі буде undefined.
Пояснення прикладу 1.4. В обох випадках ми маємо звичайну, а не стрілкову функцію. В обох випадках обійшлось без bind/call/apply. В обох випадках ті функції викликані як звичайні функції, а не як конструктори чи методи об’єкта. Але перша функція має позначку [[Strict]]: false, а друга функція має позначку [[Strict]]: true. Тому для першої функції рушій значенням this передасть window, а для другої функції значенням this рушій передасть undefined. Я спеціально додав цей приклад щоб акцентувати увагу: буде код запускатись в строгому чи в sloppy режимі визначається на етапі оголошення функції, а не виклику.
Конструктор vs Метод об’єкта vs Окрема функція
Приклад 2.1.
const object = {
logThis: function () {
console.log(this);
console.log(this === object);
},
};
object.logThis();
const object = {
logThis() {
console.log(this);
console.log(this === object);
},
};
object.logThis();
const object = {};
const logger = function () {
console.log(this);
console.log(this === object);
};
object.logThis = logger;
object.logThis();
const object = {};
object.logThis = function () {
console.log(this);
console.log(this === object);
};
object.logThis();
Приклад 2.2.
const Constructor = function () {
this.a = "a";
this.b = "b";
console.log(this);
};
const instance = new Constructor();
class SomeClass {
constructor() {
this.a = "a";
this.b = "b";
console.log(this);
}
}
const instance = new SomeClass();
Приклад 2.3.
const Constructor = function () {
this.modifyAndLogThis = 67;
console.log(this);
};
const object = {
modifyAndLogThis: Constructor,
};
object.modifyAndLogThis();
object.modifyAndLogThis();
Приклад 2.4.
const SloppyConstructor = function () {
this.randomVal = Math.random();
console.log(this);
};
const StrictConstructor = function () {
"use strict";
this.randomVal = Math.random();
console.log(this);
};
const sloppyFuncAsVariable = SloppyConstructor;
const strictFuncAsVariable = StrictConstructor;
SloppyConstructor();
console.log(window.randomVal);
sloppyFuncAsVariable();
console.log(window.randomVal);
strictFuncAsVariable();
StrictConstructor();
Пояснення прикладу 2.1. Функція не є стрілковою. bind/apply/call використані не були. Отже т.к. функція викликана як метод об’єкта, то в значення this рушій засуне сам той об’єкт. І відповідно console.log(this) виведе сам той об’єкт. А console.log(this === object) виведе true.
Пояснення прикладу 2.2. Функції не є стрілковими. Обійшлось без bind/apply/call. Оскільки функції викликані після слова new, то в значення this рушій засуне той об’єкт, який вони конструюють.
Пояснення прикладу 2.3. Функція не є стрілковою. І нема ані bind, ані call/apply, але от далі цікаво. Хоч з самого початку ми створили функцію як конструктор, але потім ми її засунули як метод об’єкта. І викликали як метод об’єкта. Відповідно при запуску цієї функції як метода об’єкта рушій значенням this передає сам той об’єкт. Тому ця функція спершу модифікує той свій об’єкт, міняє в ньому .modifyAndLogThis на число 67 і виводить його уже з {modifyAndLogThis: 67}. А тоді при наступному запуску object.modifyAndLogThis буде помилка, бо число не можна викликати.
Пояснення прикладу 2.4. Обидві функції не є стрілковими. Обидві функції не мають пришитого значення this. І обидві функції спершу були оголошені як конструктори. Але потім ми їх запустили як звичайні змінні. Тому для sloppyFuncAsVariable рушій значенням this засуне window (бо sloppy mode), а для strictFuncAsVariable рушій засуне undefined (бо strict mode).
.bind, .call, .apply
Приклад 3.1.
const sloppyFunc = function () {
console.log(this);
};
const strictFunc = function () {
"use strict";
console.log(this);
};
const Constructor = function () {
this.a = "a";
console.log(this);
};
const object = {
logThis() {
console.log(this);
},
};
// нормальні виклики
sloppyFunc();
strictFunc();
Constructor();
new Constructor();
object.logThis();
// явно вказані значення
sloppyFunc.apply({ bim: "bam" });
strictFunc.apply({ bim: "bam" });
Constructor.apply({ bim: "bam" });
// new Constructor.apply({bim: "bam"}); // помилка
object.logThis.apply({ bim: "bam" });
sloppyFunc.bind({ bom: "bem" })();
strictFunc.bind({ bom: "bem" })();
Constructor.bind({ bom: "bem" })();
// new Constructor.bind({bom: "bem"})(); // помилка
object.logThis.bind({ bom: "bem" })();
Помилки в двох місцях де я закоментував будуть тому, що конструктор не можна використовувати таким способом.
А результат і причини всіх інших місць коду думаю очевидні.
Стрілкові функції
Приклад 4.1.
console.log(this);
const sloppyArrowFunc = () => {
console.log(this);
};
sloppyArrowFunc();
const strictArrowFunc = () => {
"use strict";
console.log(this);
};
strictArrowFunc();
const object1 = {
sloppyLogThis: sloppyArrowFunc,
strictLogThis: strictArrowFunc,
};
object1.sloppyLogThis();
object1.strictLogThis();
const object2 = {
sloppyLogThis: () => {
console.log(this);
},
strictLogThis: () => {
console.log(this);
},
};
object2.sloppyLogThis();
object2.strictLogThis();
const basicArrowFunc = () => console.log(this);
basicArrowFunc.apply({});
const arrowFuncWithBindedThis = basicArrowFunc.bind({});
arrowFuncWithBindedThis();
const wrapper = {
hello: "world",
executeExperiment: function () {
console.log(this, wrapper === this);
const sloppyArrowFunc = () => {
console.log(this, wrapper === this);
};
sloppyArrowFunc();
const strictArrowFunc = () => {
"use strict";
console.log(this, wrapper === this);
};
strictArrowFunc();
const object1 = {
sloppyLogThis: sloppyArrowFunc,
strictLogThis: strictArrowFunc,
};
object1.sloppyLogThis();
object1.strictLogThis();
const object2 = {
sloppyLogThis: () => {
console.log(this, wrapper === this);
},
strictLogThis: () => {
console.log(this, wrapper === this);
},
};
object2.sloppyLogThis();
object2.strictLogThis();
const basicArrowFunc = () => console.log(this, wrapper === this);
basicArrowFunc.apply({});
const arrowFuncWithBindedThis = basicArrowFunc.bind({});
arrowFuncWithBindedThis();
},
};
wrapper.executeExperiment();
Приклад 4.2.
const ArrowConstructor = () => {
this.a = "a";
};
const instance = new ArrowConstructor();
class SomeClass {
constructor: () => {
this.a = "a";
}
}
const instance = new SomeClass();
Приклад 4.3.
function buildArrowFunc() {
const randomNumber = Math.random();
return () => console.log(this, randomNumber);
}
const object = {
buildArrowFunc, // те ж саме, що й buildArrowFunc: buildArrowFunc,
};
const arrowFunction1 = buildArrowFunc();
const arrowFunction2 = object.buildArrowFunc();
console.log(buildArrowFunc === object.buildArrowFunc);
arrowFunction1();
arrowFunction2();
Пояснення прикладу 4.1. В усіх випадках ми маємо справу з стрілковими функціями. Тому інші критерії не важливі. Якщо функція стрілкова, то ніякий call/apply/bind не допоможе. І не важливо чи викликана вона як метод, чи як окрема. І не важливо чи викликана вона в строгому чи звичайному режимі. В усіх випадках якщо функція стрілкова, то рушій буде глядіти this як звичайну змінну за межами тієї функції в межах її замикання. І тому console.log(this) за межами тих функцій і в їх межах завжди один і той самий.
Пояснення прикладу 4.2. Рушій видасть помилку в обох випадках. В першому тому, що стрілкова функція просто не може використовуватись як конструктор. А в другому взагалі помилка синтаксису.
Пояснення прикладу 4.3. Тут дуже уважно! І arrowFunction1 стрілкова, і arrowFunction2 стрілкова. І тому в обох випадках значення this глядиться як значення звичайної змінної (за замиканням). Але якраз замикання в цих функцій різні. Тому і значення this та randomNumber різні. Але т.к. замикання — це окрема тема (про яку я детально написав в попередній статті), то зараз на цьому спинятись не буду.
Підсумуємо
Для визначення this важливо пам’ятати 4 ключових критерії, 4 ключових кроки:
- Чи є функція стрілковою?
- Якщо стрілкова, то функція не може мати
this, тобтоthisбуде глядітись як звичайна змінна. - Якщо нормальна, то чи має функція явно «пришите» значення
thisз допомогоюcall/apply/bind?- Якщо має явно пришите, то воно і буде
this. - Якщо не має явно пришитого, то як саме функція запущена?
- якщо запущена як конструктор, то
thisбуде об’єкт, який конструюють - якщо запущена після крапочки як метод об’єкта, то
thisбуде об’єкт перед крапочкою - якщо запущена як окрема функція, то який режим в цій конкретній функції?
- якщо sloppy mode, то
thisбудеwindow - якщо strict mode, то
thisбудеundefined
- якщо sloppy mode, то
- якщо запущена як конструктор, то
- Якщо має явно пришите, то воно і буде
- Якщо стрілкова, то функція не може мати
Прощаємось
Це був я.
Якщо залишились питання — залишай коментар.
Якщо не було корисно — залишай коментар.
Якщо було корисно — залишай коментар + обов’язково став лайк.
Якщо знаєш якийсь класний інструмент який швидко і красиво намалює схему, то напиши будь ласка дооновлю статтю.
Ну а в мене на сьогодні все. Допобачення :)
27 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарівНа сьогодні зробити більш-менш складний SPA за допомогою AI-слопу (типу клоде тощо) без знань JavaScript — неможливо. Тільки прості і примітивні компоненти які і раніше можна було завантажити з codepen
Це не правда. Прості SPA робляться без знання коду без особливих проблем.
Методи apply та call всього навсього дозволяють викликати функцію так ніби вона є методом вказаного обʼєкту. Нащо тулити до цього якесь пришивання?
javascript та js замість JavaScript та JS ріже очі. Нащо писати implicit, explicit якщо вони легко й просто перекладаються? JavaScript найгірше з того що я зустрічав. Текст читається важко.
Не параметр а аргумент.
Якщо коротко, то this посилається на контекст виконання. В принципі все.
Дякую за статтю. Добре все пояснили.
Дякую за дуже добру статтю! Єдине що: оскільки this є в багатьох мовах (Java, C++, C#, PHP), то варто було би у заголовку статті, що в статті йдеться про JS.
Дякую за відгук і ще більше за уважність.
Я про це не подумав коли робив заголовок.
Але уже написав про це редактору, найближчим часом заголовок повинні уточнити. Дякую
Підправила у заголовку, дякую вам за чудову статтю 🙌
Спасибо за статью
По темплейтам кода хотелось бы видеть нумерацию строк в них и через комменты вывод логов
Дякую за фідбек
Коментарі для виводів спеціально не робив щоб не спойлерити. Щоб людина спершу пробувала сама зробити припущення, а тоді вставляла код в консоль і перевіряла.
Ідеально було б якби можна було одною кнопкою зразу ж відкрити типу як в телеграмі зробили коли текст зашумлений поки не клікнеш. Але платформа такої можливості не надає.
Стосовно нумерування рядків коду на жаль тут теж все впирається в платформу
У мене є мета-питання, майже серйозне. Нахіба це взагалі знати в 2026 році?
У мене колись була книжка по jQuery від Ехуда Кац, там розбиралися замикання і this. Це було років 15 тому якщо не більше. З тих пір я js не торкався і нічого не памʼятаю.
Десь рік тому я навайбкодив аппку-словник, і задав це ж саме питання нашому фронтенд-розробнику. Він відповів що в мене аппка недостатньо складна.
Окей, цього року я навайбкодив ChatGPT-like чат з графічним редактором й багатьма функціями, там досить багато фронтенду. Однозначно складніша за UI який ми робили для користувачів нашого IoT продукту на колишній роботі в Twilio.
Якщо і це занадто проста аппка, то я навіть не знаю що є складна.
Повертаючись до того з чого почав: нашо «інженеру» в 2026 році знати як працюють closure та this в js? В моєму розумінні це душе швидко стало таким же необхідним знанням як для водія трамвая знання про схему обмотки двигуна постійного струму.
Стосовно вайбкоду я намагався зрозуміти що саме ти хотів тим сказати, але так і не зміг.
Тому на ту частину коментаря відповідати не знаю що.
Стосовно:
Якщо інженер просто бездумно клацає
tab, то ні для чого.Якщо інженер все життя програмує іншими мовами, 15 років
прожив безстресуне торкався javascript і не планує цього робити в найближчому майбутньому, то теж ні для чого.Але якщо інженер сам пише/редагує код, і/або робить рев’ю написаного, причому саме javascript/typescript коду, то відповідь очевидна
Окей, тоді перефразую питання:
Ви можете навести приклади фронтендів, на яких в 2026 році інженеру доцільно самому писати/редагувати код і/або робити ревʼю написаного [на такому низькому рівні]?
Частина про вайбкод була про те що досить складні фронтенд-проекти сьогодні вже не потребують знання js. Вся ця частина — це відповідь на гіпотетичний контр-аргумент «твої проекти недостатньо складні». Ось це вже не аргумент.
Я допускаю що підтримка великих старих кодових баз на якомусь першому ангулярі все ще переповнює контекст скоріше ніж AI може щось гарне там зробити. Але це лише моє припущення, я не знаю, в цьому суть питання.
Для порівняння, на бекенді ситуація трохи інша — є певні задачі де AI сам не справляється (поки що), два приклади з моїх проектів — multithreading та всякі архітектурні рішення де потрібне scalability. Тобто якщо ви таке робите, то на інтервʼю однозначно треба перевіряти знання кандидата у цих темах.
Чому ви впевнені, що цього не може бути на web UI app?
ну бо ж «фронтендери не програмісти».
Він того не сказав прямим текстом, це не дослівна цитата, але це білою ниткою явно вимальовується крізь всю його думку.
Людина або справді застрягла в світі 15річної давності і навіть не хоче нічого нового для себе відкрити (якби хотів, давно відкрив би), або просто тролить (що як на мене більш вірогідно). Я особисто після другого його коментаря навіть не намагаюсь відповісти
Я наче задав досить просте питання. Ви чомусь почали обговорювати мене замість того щоб на нього відповісти.
Просвітіть мене, що б***ь таке scalability фронтенда?
Scalability фронтенда — це здатність системи рости (по фічах, команді, обсягу даних і складності UI) без експоненційного зростання складності коду.
На відміну від загального ПЗ, у фронтенді це особливо проявляється через:
— керування станом
— повторне використання компонентів
— розділення бізнес-логіки та UI
— контроль розміру бандлів і продуктивності
Якщо при додаванні нових фіч код стає крихким, з’являється копіпаста і важко вносити зміни — такий фронтенд не є scalable.
Аналогічно до будьякого ПЗ, є загальні проблеми та особисті.
З вашим питанням ШІ теж порається непогано.
Це не scalability. Те що ви описали називається maintainability.
Якщо ваше бачення maintainability одразу включає scalability за замовчуванням, нехай так і буде.
Ну якщо бездумно вайбкодити для себе якусь апку — не обов’язково і інженером бути)
Не жаваскріпт девелопер ет олл, але прочитати було цікаво. Років 7 тому трохи дискутували з хлопчиськами на цю тему, а ось і структурне пояснення, чи актуально воно мені зараз — ні, чи цікаво — так. Бтв, зараз такий ринок, що у водія трамваю який шарить в схемі обмотки, значно більше шансів на файний офер, особливо якщо ще і козеріг)
Якще не хочешь щоб інтефейс твоєї апки зжирав половину оперативної пам’яті то знання js лишнім не буде, хоча там не тільки js.
Навайбкодити можна багато чого аби працювало, а от аби воно працювало добре і щоб код був максимально добре написаним, то все ще треба його після ШІ ревʼювати і правити. JQuery це просто ліба, сьогодні одна ліба на висоті, завтра — інша, а замикання в JS це база, хоч може і не використовуватися щоденно, і ця база буде присутня в незалежності від того яку лібу ви будете використовувати, вам ж треба розуміти код, який ШІ написав.
Верстку можна було робити без коду вже, напевно, років 20 як. Більшість вебдеву зводяться до наклєпать вже готові віджети в правильних місцях, скопіювати якийсь стиль і підключить БД. Це все вже робилося мільйон разів до цього. Ви коли вайбкодите, то ви підлаштовуєте ваші вимоги під те що вам видає ШІ. У реальному світі є вимоги і хотєлкі які не підлаштовуються під вас. Я не раз бачив як «вайбкодери» ДНЯМИ переписували промпт щоб попасти у специфікацію або виправити баг. Знати базу програмування треба для того, щоб вирішувати задачі де ШІ застрягне, очевидно.
P.S. Ну і більшість верстальщиків інженерами можна назвати з великою натяжкою в принципі.
А чи є різниця між «зазубрити правильно» і «зрозуміти», якщо правила все одно абсолютно нелогічні?
все залежить від де бачити межу між зазубрити і зрозуміти.
Щось трохи та й запам’ятати всеодно доведеться (власне послідовність тих 4 умов). Але це явно не те ж, що зубрити задачки яка коли що дасть