Невозможно разгадать тайну функций в Javascript

16

Я пытаюсь понять за кулисами сцены Javascript и застрял в понимании создания встроенных объектов, особенно Объекта и Функции, и отношений между ними.

Когда я прочитал, что все встроенные объекты, такие как Array, String и т. Д., Являются расширением (унаследованным) от Object, я предположил, что Object является первым встроенным объектом, который создается, а остальные объекты наследуют от него. Но это не имеет смысла, когда вы узнаете, что Объекты могут быть созданы только функциями, но тогда функции также являются не чем иным, как объектами Function. Это начало звучать как дилемма курицы и курицы.

Другая чрезвычайно запутанная вещь заключается в том, что если console.log(Function.prototype)я печатаю функцию, но когда я печатаю, console.log(Object.prototype)она печатает объект. Почему Function.prototypeфункция, когда она должна была быть объектом?

Кроме того, согласно документации Mozilla, каждый javascript functionявляется расширением Functionобъекта, но когда вы console.log(Function.prototype.constructor)снова - это функция. Теперь, как вы можете использовать что-то, чтобы создать это самостоятельно (Mind = Blowed).

И последнее, Function.prototypeэто функция, но я могу получить доступ к constructorфункции Function.prototype.constructor, значит ли этоFunction.prototype функцию, которая возвращает prototypeобъект

Умайр Абид
источник
поскольку функция является объектом, это означает, что она Function.prototypeможет быть функцией и иметь внутренние поля. Так что нет, вы не выполняете функцию прототипа при прохождении ее структуры. Наконец, помните, что есть движок, интерпретирующий Javascript, поэтому объект и функция, вероятно, создаются внутри движка, а не из Javascript и специальных ссылок Function.prototypeи Object.prototypeмогут просто интерпретироваться движком специальным образом.
Вальфрат
1
Если вы не хотите реализовать совместимый со стандартами компилятор JavaScript, вам не нужно беспокоиться об этом. Если вы ищете что-то полезное, вы не в курсе.
Джаред Смит
5
К вашему сведению, в английском языке обычная фраза «дилемма курицы и курицы» - «проблема курицы и яйца», а именно: «что появилось раньше: курица или яйцо?» (Конечно, ответ - яйцо. Яйцекладущие животные были вокруг за миллионы лет до кур).
Эрик Липперт

Ответы:

32

Я пытаюсь понять, за кулисами Javascript, и застрял в понимании создания встроенных объектов, особенно Объекта и Функции, и отношений между ними.

Это сложно, это легко понять неправильно, и многие книги для начинающих по Javascript ошибаются, поэтому не доверяйте всему, что читаете.

Я был одним из разработчиков движка Microsoft JS в 1990-х годах и в комитете по стандартизации, и я сделал несколько ошибок, составив этот ответ. (Хотя, поскольку я не работал над этим более 15 лет, я, возможно, могу быть прощен.) Это сложная вещь. Но как только вы понимаете наследование прототипа, все становится понятным.

Когда я прочитал, что все встроенные объекты, такие как Array, String и т. Д., Являются расширением (унаследованным) от Object, я предположил, что Object является первым встроенным объектом, который создается, а остальные объекты наследуют от него.

Начните с отбрасывания всего, что вы знаете о наследовании на основе классов. JS использует наследование на основе прототипов.

Затем убедитесь, что у вас есть очень четкое определение того, что означает «наследование». Люди, привыкшие к ОО-языкам, таким как C # или Java или C ++, думают, что наследование означает подтип, но наследование не означает подтип. Наследование означает, что члены одной вещи также являются членами другой вещи . Это не обязательно означает, что между этими вещами есть отношения подтипов! Так много недоразумений в теории типов являются результатом того, что люди не понимают, что есть разница.

Но это не имеет смысла, когда вы узнаете, что Объекты могут быть созданы только функциями, но тогда функции также являются не чем иным, как объектами Function.

Это просто ложь. Некоторые объекты не создаются путем вызова new Fкакой-либо функции F. Некоторые объекты создаются средой выполнения JS из ничего. Есть яйца, которые не были отложены ни одной курицей . Они были просто созданы во время выполнения, когда он запускался.

Давайте скажем, каковы правила и, возможно, это поможет.

  • Каждый экземпляр объекта имеет прототип объекта.
  • В некоторых случаях этот прототип может быть null.
  • Если вы обращаетесь к члену в экземпляре объекта, и у объекта нет этого члена, тогда объект обращается к своему прототипу или останавливается, если прототип имеет значение null.
  • prototypeЧленом объекта , как правило , не прототип объекта.
  • Скорее, prototypeчлен функционального объекта F является объектом, который станет прототипом объекта, созданного с помощью new F().
  • В некоторых реализациях экземпляры получают __proto__член, который действительно дает свой прототип. (Это устарело. Не надейтесь на это.)
  • Объекты функций получают совершенно новый объект по умолчанию, назначенный prototypeпри их создании.
  • Прототип функционального объекта, конечно Function.prototype.

Давайте подведем итоги.

  • Прототип ObjectявляетсяFunction.prototype
  • Object.prototype является объектом-прототипом объекта.
  • Прототип Object.prototypeявляетсяnull
  • Прототип FunctionIS 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отеля? Итак, мы смотрим на его прототип.
  • Прототип Animalis 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это функция.

Теперь, как вы можете использовать что-то, чтобы создать это самостоятельно (Mind = Blowed).

Вы слишком обдумываете это . Все, что требуется, - это то, что среда выполнения создает кучу объектов при запуске. Объекты - это просто таблицы поиска, которые связывают строки с объектами. Когда среда запускается, все это нужно сделать , это создать несколько десятков пустых объектов, а затем начать присваивающей 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: Сначала я согласен, что это выглядит сложно, но сложность является следствием простых правил. Каждый объект имеет __proto__. __proto__Объект - прототип является недействительным. __proto__Из new X()вне X.prototype. Все функциональные объекты имеют прототип функции, __proto__кроме самого прототипа функции. Objectи Functionи прототип функции являются функциями. Все эти правила просты и определяют топологию графа исходных объектов.
Эрик Липперт
6

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

МАГИЯ !!!

На самом деле, это все.

Люди, которые реализуют механизмы исполнения ECMAScript, должны реализовывать правила ECMAScript, но не соблюдать их в своей реализации.

Спецификация ECMAScript говорит, что A наследуется от B, но B является экземпляром A? Нет проблем! Сначала создайте 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% и начальным значением свойства Function глобального объекта.

Ewan
источник
Теперь это так. ECMA6 позволяет вам создавать классы и создавать из них объекты.
ncmathsadist
2
@ncmathsadist Классы ES6 - просто синтаксический сахар, семантика одинакова.
Хамза Фатми
1
В sarcasmпротивном случае ваш текст действительно непрозрачен для новичка.
Роберт Харви
правда, позже я добавлю больше, нужно немного покопаться
Ewan
1
эм? указать, что это не ясно из документа
Ewan
1

Следующие типы охватывают каждое значение в JavaScript:

  • boolean
  • number
  • undefined(который включает в себя одно значение undefined)
  • string
  • symbol (абстрактные уникальные «вещи», которые сравниваются по ссылке)
  • object

Каждый объект (то есть все) в JavaScript имеет прототип, который является своего рода объектом.

Прототип содержит функции, которые также являются своего рода объектом 1 .

У объектов также есть конструктор, который является функцией и, следовательно, своего рода объектом.

вложенный

Это все рекурсивно, но реализация способна сделать это автоматически, потому что, в отличие от кода JavaScript, она может создавать объекты без вызова функций JavaScript (поскольку объекты - это просто память, которой управляет реализация).

Большинство объектных систем во многих динамически типизированных языках имеют круглую форму 2, как это. Например, в Python классы - это объекты, а класс классов - это type, typeследовательно, его экземпляр.

Лучшая идея - просто использовать инструменты, которые предоставляет язык, и не слишком задумываться о том, как они туда попали.

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

2 На самом деле это скорее измученная ветвящаяся лента, согнутая назад над собой, но «круглая» достаточно близко.

Challenger5
источник