Возьмите эти 2 примера:
var A = function() { this.hey = function() { alert('from A') } };
против
var A = function() {}
A.prototype.hey = function() { alert('from prototype') };
Большинство людей здесь (особенно ответы с самым высоким рейтингом) пытались объяснить, чем они отличаются, не объясняя ПОЧЕМУ. Я думаю, что это неправильно, и если вы сначала поймете основы, разница станет очевидной. Давайте сначала попробуем объяснить основы ...
а) Функция - это объект в JavaScript. КАЖДЫЙ объект в JavaScript получает внутреннее свойство (то есть, вы не можете получить к нему доступ, как и к другим свойствам, за исключением, может быть, в браузерах, таких как Chrome), часто называемое __proto__
(вы действительно можете набрать anyObject.__proto__
в Chrome, чтобы увидеть, что это ссылки. Это просто свойство, ничего более. Свойство в JavaScript = переменная внутри объекта, ничего более. Что делают переменные? Они указывают на вещи.
Так на что же указывает это свойство __proto__
? Ну, обычно другой объект (мы объясним, почему позже). Единственный способ заставить JavaScript для свойства __proto__
НЕ указывать на другой объект - это использовать var newObj = Object.create(null)
. Даже если вы сделаете это, свойство __proto__
STILL существует как свойство объекта, просто оно не указывает на другой объект, оно указывает на null
.
Вот где большинство людей запутываются:
Когда вы создаете новую функцию в JavaScript (которая также является объектом, помните?), В момент ее определения JavaScript автоматически создает новое свойство для этой функции с именем prototype
. Попробуйте:
var A = [];
A.prototype // undefined
A = function() {}
A.prototype // {} // got created when function() {} was defined
A.prototype
ПОЛНОСТЬЮ ОТЛИЧАЕТСЯ от свойства __proto__
. В нашем примере «A» теперь имеет ДВА свойства, называемые «prototype» и __proto__
. Это большая путаница для людей. Свойства prototype
и __proto__
никак не связаны, это разные вещи, указывающие на отдельные значения.
Вы можете задаться вопросом: почему JavaScript имеет свойство __proto__
, созданное для каждого отдельного объекта? Ну, одним словом: делегирование . Когда вы вызываете свойство объекта, а у объекта его нет, JavaScript ищет объект, на который ссылается __proto__
, чтобы выяснить, есть ли у него его. Если у него его нет, он смотрит на свойство __proto__
этого объекта и так далее ... до тех пор, пока цепочка не закончится. При этом название прототип цепи . Конечно, если __proto__
не указывает на объект и вместо этого указывает на null
, то, к сожалению, JavaScript это поймет и вернет вам undefined
для свойства.
Вы также можете задаться вопросом, почему JavaScript создает свойство с именем prototype
для функции, когда вы определяете функцию? Поскольку он пытается обмануть вас, да обманывает вас , что он работает как языки на основе классов.
Давайте продолжим с нашим примером и создадим «объект» из A
:
var a1 = new A();
Что-то происходит на заднем плане, когда это происходит. a1
- обычная переменная, которой был присвоен новый пустой объект.
Тот факт, что вы использовали оператор new
до вызова функции A()
, сделал что-то ДОПОЛНИТЕЛЬНОЕ в фоновом режиме. Ключевое слово new
создало новый объект, который теперь ссылается на a1
, и этот объект пуст. Вот что происходит дополнительно:
Мы говорили, что в каждом определении функции создается новое свойство с именем prototype
(к которому вы можете обращаться, в отличие от свойства __proto__
)? Ну, это свойство сейчас используется.
Итак, мы находимся в точке, где у нас есть свежеиспеченный пустой a1
объект. Мы сказали, что все объекты в JavaScript имеют внутреннее свойство __proto__
, которое указывает на что-то (a1
также имеет это), независимо от того, является ли оно нулевым или другим объектом. Оператор new
устанавливает то свойство __proto__
, которое указывает на свойство prototype
функции. Прочитайте это снова. Это в основном это:
a1.__proto__ = A.prototype;
Мы сказали, что A.prototype
- не более чем пустой объект (если мы не изменим его на что-то еще до определения a1
). Так что теперь a1.__proto__
указывает на то же самое, на что указывает A.prototype
, то есть этот пустой объект. Они оба указывают на один и тот же объект, который был создан, когда произошла эта строка:
A = function() {} // JS: cool. let's also create A.prototype pointing to empty {}
Теперь, когда обрабатывается оператор var a1 = new A()
, происходит еще одна вещь. В основном A()
выполняется, и если A что-то вроде этого:
var A = function() { this.hey = function() { alert('from A') } };
Все эти вещи внутри function() { }
будут выполнены. Когда вы достигаете линии this.hey..
, this
изменяется на a1
, и вы получаете это:
a1.hey = function() { alert('from A') }
Я не буду объяснять, почему this
меняется на a1
, но это отличный ответ , чтобы узнать больше.
Итак, подведем итог, когда вы делаете var a1 = new A()
, на заднем плане происходит 3 вещи:
- Совершенно новый пустой объект создается и присваивается
a1
. a1 = {}
a1.__proto__
свойство присваивается так же, как указывает A.prototype
(другой пустой объект {})
Функция A()
выполняется с this
, установленным на новый пустой объект, созданный на шаге 1 (прочитайте ответ, на который я ссылался выше, чтобы узнать, почему this
меняется на a1
)
Теперь давайте попробуем создать еще один объект:
var a2 = new A();
Шаги 1,2,3 будут повторяться. Вы что-то замечаете? Ключевое слово repeat. Шаг 1: a2
будет новым пустым объектом, шаг 2: его свойство __proto__
будет указывать на то же самое, на что A.prototype
указывает и, что наиболее важно, шаг 3 : функция A()
СНОВА выполняется, что означает, что a2
получит свойство hey
, содержащее функцию. a1
и a2
имеют два свойства SEPARATE с именем hey
, которые указывают на 2 отдельные функции! Теперь у нас есть дубликаты функций в одних и тех же двух разных объектах, делающих одно и то же, упс ... Вы можете представить последствия для памяти этого, если у нас есть 1000 объектов, созданных с new A
, после того как все объявления функций занимают больше памяти, чем что-то вроде числа 2. Так как мы можем предотвратить это?
Помните, почему свойство __proto__
существует для каждого объекта? Таким образом, если вы извлекаете свойство yoMan
в a1
(которое не существует), будет рассматриваться его свойство __proto__
, которое, если это объект (и в большинстве случаев так оно и есть), проверит, содержит yoMan
, и если он этого не делает, он сверится с __proto__
этого объекта и т. д. Если это произойдет, он примет значение этого свойства и отобразит его для вас.
Поэтому кто-то решил использовать этот факт + тот факт, что при создании a1
его свойство __proto__
указывает на тот же (пустой) объект, на который указывает A.prototype
, и делает это:
var A = function() {}
A.prototype.hey = function() { alert('from prototype') };
Cool! Теперь, когда вы создаете a1
, он снова проходит через все 3 шага, описанных выше, и на шаге 3 он ничего не делает, так как function A()
не имеет ничего для выполнения. И если мы сделаем:
a1.hey
Он увидит, что a1
не содержит hey
, и проверит свой объект свойства __proto__
, чтобы увидеть, есть ли он, что имеет место.
При таком подходе мы исключаем часть из шага 3, где функции дублируются при каждом создании нового объекта. Вместо a1
и a2
, имеющих отдельное свойство hey
, теперь ни у одного из них его нет. Который, я полагаю, ты уже сам понял. Это хорошо ... если вы понимаете __proto__
и Function.prototype
, подобные вопросы будут довольно очевидными.
ПРИМЕЧАНИЕ: Некоторые люди склонны не называть внутреннее свойство Prototype как __proto__
, я использовал это имя в посте, чтобы четко отличить его от свойства Functional.prototype
как две разные вещи.