Неможливо вирішити таємницю функцій у Javascript


16

Я намагаюся зрозуміти за завісовими сценами Javascript і свого роду застряг у розумінні створення вбудованих об'єктів, особливо Об'єкта і Функції та співвідношення між ними.

Коли я прочитав, що всі вбудовані об'єкти, такі як Array, String тощо, є розширенням (успадкованим) від Object, я припустив, що Object є першим вбудованим об'єктом, який створюється, а решта об'єктів успадковується від нього. Але це не має сенсу, коли ви дізнаєтесь, що Об'єкти можна створювати лише за допомогою функцій, але тоді функції - це не що інше, як об'єкти Функції. Це почало звучати як дилема з куркою та куркою.

Інша надзвичайно заплутана річ - це, якщо я console.log(Function.prototype)друкує функцію, але коли я друкую, console.log(Object.prototype)вона друкує об'єкт. Чому Function.prototypeфункція, коли вона мала бути предметом?

Крім того, згідно з документацією на Mozilla, кожен javascript function- це розширення Functionоб'єкта, але коли ви console.log(Function.prototype.constructor)знову - це функція. Тепер, як ви можете використовувати щось для створення себе (Розум = здутий).

Останнє Function.prototype- це функція, але я можу отримати доступ до constructorфункції за допомогою Function.prototype.constructorозначає, що Function.prototypeце функція, яка повертає prototypeоб'єкт


оскільки функція є об'єктом, це означає, що Function.prototypeможе бути функцією і мати внутрішні поля. Тому ні, ви не виконуєте функцію прототипу, переглядаючи її структуру. Нарешті, пам’ятайте, що існує двигун, що інтерпретує Javascript, тому «Об’єкти та функції», ймовірно, створюються всередині двигуна, а не з Javascript та спеціальних довідок, як, Function.prototypeі Object.prototypeможе просто інтерпретуватися двигуном особливим чином.
Вальфрат

1
Якщо ви не хочете реалізувати компілятор JavaScript, сумісний із стандартами, вам справді не потрібно турбуватися про ці речі. Якщо ви хочете зробити щось корисне, ви поза курсом.
Джаред Сміт

5
FYI, звичайна фраза англійською мовою для "дилеми з куркою та куркою" - це "проблема з куркою та яйцями", а саме "хто прийшов першим, курка чи яйце?" (Зрозуміло, відповідь - яйце. Непрохідні тварини існували довгі мільйони років до курей.)
Ерік Ліпперт,

Відповіді:


32

Я намагаюся зрозуміти за завісою сценами Javascript і свого роду застряг у розумінні створення вбудованих об'єктів, особливо Об'єкта і Функції та співвідношення між ними.

Це складно, його легко зрозуміти, і дуже багато початківців Javascript-книжок помиляються, тому не довіряйте всьому прочитаному.

Я був одним із впроваджувачів двигуна JS Microsoft у 90-х роках та в комітеті зі стандартизації, і я допустив ряд помилок, збираючи цю відповідь. (Хоча, оскільки я над цим не працював більше 15 років, можливо, мені пробачать.) Це складні речі. Але як тільки ви зрозумієте успадкування прототипу, це все має сенс.

Коли я прочитав, що всі вбудовані об'єкти, такі як Array, String тощо, є розширенням (успадкованим) від Object, я припустив, що Object є першим вбудованим об'єктом, який створюється, а решта об'єктів успадковується від нього.

Почніть з викидання всього, що ви знаєте про спадкування на основі класу. JS використовує спадкування на основі прототипу.

Далі переконайтеся, що у вас в голові є дуже чітке визначення того, що означає «спадщина». Люди, які звикли до мов OO, таких як C # або Java або C ++, вважають, що успадкування означає субтипізацію, але успадкування не означає підтипізацію. Спадщина означає, що члени однієї речі також є членами іншої речі . Це не обов'язково означає, що між цими речами існує взаємозв'язок! Так багато непорозумінь в теорії типів є результатом того, що люди не розуміють, що є різниця.

Але це не має сенсу, коли ви дізнаєтесь, що Об'єкти можна створювати лише за допомогою функцій, але тоді функції - це не що інше, як об'єкти Функції.

Це просто помилково. Деякі об'єкти не створюються за допомогою виклику new Fпевної функції F. Деякі об'єкти створюються в процесі виконання JS взагалі з нічого. Є яйця, які не відклала жодна курка . Вони були просто створені під час виконання, коли він запускався.

Скажімо, які правила є, і, можливо, це допоможе.

  • Кожен екземпляр об'єкта має об'єкт-прототип.
  • У деяких випадках прототипом може бути null.
  • Якщо ви отримуєте доступ до члена в екземплярі об'єкта, а об’єкт не має цього члена, то об'єкт переходить до свого прототипу або зупиняється, якщо прототип є нульовим.
  • prototypeЧленом об'єкта , як правило , НЕ прототип об'єкта.
  • Скоріше, prototypeчлен функціонального об'єкта F - це об'єкт, який стане прототипом об'єкта, створеного new F().
  • У деяких реалізаціях екземпляри отримують __proto__член, який дійсно надає свій прототип. (Це тепер застаріло. Не покладайтеся на це.)
  • Об'єкти функцій отримують абсолютно новий об'єкт за замовчуванням, призначений під prototypeчас їх створення.
  • Зрозуміло, прототип функціонального об'єкта Function.prototype.

Давайте підведемо підсумки.

  • Прототипом ObjectєFunction.prototype
  • Object.prototype є об'єктом прототипу об'єкта.
  • Прототипом Object.prototypeєnull
  • Прототип Functionє Function.prototype- це одна з рідкісних ситуацій, коли Function.prototypeнасправді є прототипом Function!
  • Function.prototype є об'єктом прототипу функції.
  • Прототипом Function.prototypeєObject.prototype

Припустимо, ми робимо функцію Foo.

  • Прототипом Fooє Function.prototype.
  • Foo.prototype є об'єктом прототипу Foo.
  • Прототипом Foo.prototypeє Object.prototype.

Припустимо, ми говоримо new Foo()

  • Прообразом нового об’єкта є Foo.prototype

Переконайтесь, що це має сенс. Давайте намалюємо це. Овали - це об'єктні екземпляри. Краї є або __proto__означають "прототип", або prototypeозначають " prototypeвластивість".

введіть тут опис зображення

Все, що потрібно виконати, - це створити всі ці об'єкти і відповідно призначити їх різні властивості. Я впевнений, що ви можете бачити, як це було б зробити.

Тепер давайте розглянемо приклад, який перевіряє ваші знання.

function Car(){ }
var honda = new Car();
print(honda instanceof Car);
print(honda.constructor == Car);

Що це друкує?

Ну, що instanceofозначає? honda instanceof Carозначає " Car.prototypeдорівнює будь-якому об'єкту в hondaланцюзі прототипу '?"

Так. hondaПрототип є Car.prototype, тому ми зробили. Це друкує правду.

А як щодо другого?

honda.constructorне існує, тому ми консультуємося з прототипом, який є Car.prototype. Коли Car.prototypeоб’єкт був створений, йому автоматично було надано властивість, constructorрівне Car, тож це правда.

А що з цим?

var Animal = new Object();
function Reptile(){ }
Reptile.prototype = Animal;
var lizard = new Reptile();
print(lizard instanceof Reptile);
print(lizard.constructor == Reptile);

Що друкує ця програма?

Знову ж таки, lizard instanceof Reptileозначає " Reptile.prototypeдорівнює будь-якому об'єкту в lizardланцюзі прототипу '?"

Так. lizardПрототип є Reptile.prototype, тому ми зробили. Це друкує правду.

А тепер про що

print(lizard.constructor == Reptile);

Ви можете подумати, що це також друкує правду, оскільки lizardбуло побудовано з, new Reptileале ви будете помилятися. Обґрунтуйте це.

  • Чи lizardмає constructorмайно? Ні. Тому ми розглянемо прототип.
  • Прототип lizardє Reptile.prototype, який є Animal.
  • Чи Animalмає constructorмайно? Ні. Отже, ми подивимось, що це прототип.
  • Прототип Animalє Object.prototype, і Object.prototype.constructorстворюється під час виконання та дорівнює Object.
  • Так що це друкує помилково.

Ми повинні були сказати Reptile.prototype.constructor = Reptile;в якийсь момент там, але ми цього не пам’ятали!

Переконайтесь, що для вас все має сенс. Намалюйте кілька полів і стрілок, якщо це все ще заплутано.

Інша надзвичайно заплутана річ - це, якщо я console.log(Function.prototype)друкує функцію, але коли я друкую, console.log(Object.prototype)вона друкує об'єкт. Чому Function.prototypeфункція, коли вона мала бути предметом?

Прототип функції визначається як функція, яка при виклику повертається undefined. Ми вже знаємо, що Function.prototypeце Functionпрототип, як не дивно. Отже, Function.prototype()це законно, і коли ти це робиш, ти undefinedповертаєшся. Отже, це функція.

ObjectПрототип не володіє цією властивістю; це не можна телефонувати. Це просто предмет.

коли ти console.log(Function.prototype.constructor)це знову функція.

Function.prototype.constructorпросто Function, очевидно. І Functionце функція.

Тепер, як ви можете використовувати щось для створення себе (Розум = здутий).

Ви надмірно думаєте про це . Все, що потрібно, це те, що час запуску створює купу об’єктів при його запуску. Об'єкти - це просто таблиці пошуку, які пов'язують рядки з об'єктами. Коли середовище запускається, все це потрібно зробити , це створити кілька десятків порожніх об'єктів, а потім почати присвоює prototype, __proto__, constructorі так далі властивості кожного об'єкта , поки вони не роблять графік , що їм потрібно зробити.

Буде корисно, якщо ви візьмете ту схему, яку я вам подав вище, і додати constructorдо неї краї. Ви швидко переконаєтесь, що це дуже простий графік об'єкта і що його час не матиме жодних проблем створити його.

Гарною вправою було б зробити це самостійно. Ось, я розпочну вас. Ми будемо використовувати my__proto__для позначення "об'єкт прототипу" та myprototype"властивість прототипу".

var myobjectprototype = new Object();
var myfunctionprototype = new Object();
myfunctionprototype.my__proto__ = myobjectprototype;
var myobject = new Object();
myobject.myprototype = myobjectprototype;

І так далі. Чи можете ви заповнити решту програми для побудови набору об'єктів, що має ту саму топологію, що і "справжні" вбудовані об'єкти Javascript? Якщо ви зробите це, то ви побачите, що це надзвичайно просто.

Об'єкти в JavaScript - це просто таблиці пошуку, які пов'язують рядки з іншими об'єктами . Це воно! Тут немає ніякої магії. Ви зав'язуєте себе у вузлах, тому що уявляєте обмеження, яких насправді не існує, як, що кожен об'єкт повинен був створювати конструктор.

Функції - це просто об'єкти, які мають додаткову можливість: викликатись. Тому перегляньте свою маленьку програму моделювання та додайте .mycallableдо кожного об'єкта властивість, яка вказує, чи можна її називати чи ні. Це так просто.


9
Нарешті, коротке, стисле, зрозуміле пояснення JavaScript! Відмінно! Як би хтось із нас міг заплутатися? :) Хоча з усією серйозністю, останній шматочок щодо об'єктів, які є таблицями пошуку, є дійсно ключовим. Існує метод до божевілля --- але це все ще безумство ...
Грег Бургхардт

4
@GregBurghardt: Я погоджуюся, що спочатку це виглядає складно, але складність є наслідком простих правил. Кожен об’єкт має a __proto__. __proto__Об'єкт - прототип є недійсним. __proto__З new X()поза X.prototype. Усі об'єкти функцій мають прототип функції, за __proto__винятком самого прототипу функції. Objectі Functionпрототип функції - функції. Усі ці правила прямолінійні, і вони визначають топологію графіка початкових об'єктів.
Ерік Ліпперт

6

У вас вже є багато відмінних відповідей, але я просто хочу дати коротку і чітку відповідь на вашу відповідь про те, як все це працює, і ця відповідь:

МАГІЧНИЙ !!!

Дійсно, це все.

Люди, які реалізують двигуни виконання ECMAScript, повинні виконувати правила ECMAScript, але не дотримуватися їх у процесі їх реалізації.

Специфікація ECMAScript говорить, що A успадковує B, але B є екземпляром A? Без проблем! Створіть спочатку прототип вказівника на NULL, створіть B як екземпляр A, а потім закріпіть вказівник прототипу A, щоб потім вказувати на B. Простенька.

Ви кажете, але зачекайте, немає можливості змінити покажчик прототипу в ECMAScript! Але ось що: цей код не працює у механізмі ECMAScript, цей код є двигуном ECMAScript. У нього є доступ до внутрішніх об'єктів, у яких код ECMAScript працює на двигуні. Коротше кажучи: він може робити все, що завгодно.

До речі, якщо ви дійсно цього хочете, вам потрібно зробити це лише один раз: після цього, ви можете, наприклад, скинути внутрішню пам'ять і завантажувати цей дамп кожен раз, коли ви запускаєте двигун ECMAScript.

Зауважте, що все це все ще стосується, навіть якщо сам механізм ECMAScript був написаний в ECMAScript (як, власне, це стосується Mozilla Narcissus). Навіть тоді код ECMAScript, який реалізує двигун, все ще має повний доступ до двигуна, який він реалізує , хоча, звичайно, не має доступу до двигуна, на якому він працює .


3

З специфікацій ECMA 1

ECMAScript не містить належних класів, таких як C ++, Smalltalk або Java, але, скоріше, підтримує конструктори, які створюють об'єкти, виконуючи код, який виділяє сховище для об'єктів, і ініціалізує всі або частину їх, присвоюючи початкові значення їх властивостям. Усі функції, включаючи конструктори, є об'єктами, але не всі об'єкти є конструкторами.

Я не бачу, як це могло бути більш зрозумілим !!! </sarcasm>

Далі ми бачимо:

Прототип Прототип - це об'єкт, який використовується для реалізації спадкування структури, стану та поведінки в ECMAScript. Коли конструктор створює об'єкт, цей об'єкт неявно посилається на пов'язаний прототип конструктора з метою вирішення посилань на властивості. Асоційований прототип конструктора може посилатися на програмний вираз constructor.prototype, а властивості, додані до прототипу об'єкта, передаються шляхом успадкування всіма об'єктами, що поділяють прототип.

Отже, ми можемо бачити, що прототип є об'єктом, але не обов'язково об'єктом функції.

Також у нас є цей цікавий титбіт

http://www.ecma-international.org/ecma-262/8.0/index.html#sec-object-objects

Конструктор Object - це внутрішній об'єкт% Object% і початкове значення властивості Object глобального об'єкта.

і

Конструктор функцій - це внутрішній об'єкт% Function% та початкове значення властивості Function глобального об'єкта.


Тепер це робить. ECMA6 дозволяє створювати класи та інстанціювати об’єкти з них.
ncmathsadist

2
@ncmathsadist ES6 класи - це лише синтаксичний цукор, семантика - однакова.
Хамза Фатмі

1
У вашому sarcasmвипадку, цей текст справді є досить непрозорим для початківця.
Роберт Харві

правда, недобре додати більше пізніше, потрібно викопати копання
Еван,

1
ем? зазначити, що це не зрозуміло з документа
Еван

1

Наступні типи охоплюють кожне значення JavaScript:

  • boolean
  • number
  • undefined(що включає єдине значення undefined)
  • string
  • symbol (абстрактні унікальні "речі", які порівнюються за посиланням)
  • object

Кожен об’єкт (тобто все) в JavaScript має прототип, який є своєрідним об'єктом.

Прототип містить функції, які також є своєрідним об'єктом 1 .

Об'єкти також мають конструктор, який є функцією, а отже, і своєрідним об’єктом.

вкладені

Це все рекурсивно, але реалізація здатна робити це автоматично, тому що, на відміну від коду JavaScript, вона може створювати об'єкти без необхідності виклику функцій JavaScript (оскільки об’єкти - це просто пам'ять, якою керує реалізація).

Більшість об'єктних систем у багатьох динамічно набраних мовах мають кругову форму 2 . Наприклад, у Python класи - це об'єкти, а клас класів - це type, таким typeчином, примірник сам по собі.

Найкраща ідея - просто використовувати інструменти, які надає мова, і не думати надто про те, як вони туди потрапили.

1 Функції є досить особливими, оскільки вони можуть дзвонити, і вони є єдиними значеннями, які можуть містити непрозорі дані (їх тіло та, можливо, закриття).

2 Це насправді більше замучена, розгалужена стрічка, зігнута назад над собою, але "кругла" досить близька.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.