Разница существенная, но по сути оба являются объектами JavaScript, хотя и с разными свойствами и значениями. Перечисление всех различий на уровне языка было бы долгой историей, и было бы разумно прочитать и понять на наследование на основе прототипов JavaScript, но наиболее важные отличия заключаются в следующем:
Page
— это объект-прототип: всякий раз, когда вы создаете объект класса Page
с помощью new Page()
, функция конструктора вызывается с this
, относящимся к создаваемому объекту, а не к самому Page
. Любое свойство, к которому вы обращаетесь в объекте, ищется в так называемой «цепочке прототипов», включая так называемый объект-прототип. Доступ к этому объекту-прототипу можно получить с помощью Page.prototype
, и фактически все методы, которые вы определяете в классе Page
, также являются свойствами этого объекта-прототипа. В отличие от собственных свойств, предназначенных для ссылки на уникальные объекты или примитивы, специфичные для объекта, функции в JavaScript не должны быть привязаны к объекту во время создания объекта или функции, и могут использоваться совместно между объектами (принадлежащими того же класса, например) и вызываются для фактического экземпляра, а не для прототипа, которому они могут принадлежать. Другими словами, this.nameInput
в вашем конструкторе фактически добавляет свойство с именем nameInput
к объекту, создаваемому с помощью new Page()
, а не к прототипу, в то время как сам конструктор (constructor
) и любые нестатические методы, которые вы можете добавить в Page
, будут добавлены как свойства объекта. Page.prototype
. Между прочим, доступ к конструктору осуществляется как Page.prototype.constructor
, как и следовало ожидать. Кстати, Page.prototype.constructor === Page
равно true
.
Выражение формы, подобной { nameInput: ... }
, создает объект, прототипом которого является Object.prototype
, на практике самая основная форма объекта без «прототипа» и, следовательно, без суперкласса или каких-либо признаков, кроме того, что может предоставить объект-прототип фундаментального объекта. Любые свойства любого такого объекта { ... }
, которые могут иметься в его цепочке прототипов, включая методы, являются свойствами Object.prototype
. Вот почему вы можете выполнять ({}).toString()
или ({}).hasOwnProperty("foobar")
, фактически не имея свойств toString
или hasOwnProperty
в вашем объекте — toString
и hasOwnProperty
являются свойствами Object.prototype
, относящимися к двум различным методам, называемым toString
и hasOwnProperty
соответственно, а JavaScript создает специальное свойство для ваш объект под названием __proto__
ссылается на Object.prototype
. Вот как он знает, как «пройти цепочку прототипов». Между прочим, сами имена функций не имеют значения — я могу добавить к объекту свойство, ссылающееся на анонимную функцию: var foo = ({}); foo.bar = function() { };
и вызвать указанную безымянную функцию с помощью foo.bar()
.
Одна ошибка, которую вы, по-видимому, совершаете, заключается в том, что вы путаете объект класса с классом, иначе вы бы не сравнивали export default class Page { ... }
с export const Page = { nameInput: Selector(...) }
- первый создает класс, доступный как Page
, который используется в качестве объекта-прототипа всякий раз, когда объекты класса создается, а последний создает объект, доступный как Page
, который содержит nameInput
, относящийся к результату вычисления выражения Selector("#developer-name")
(вызов Selector
с единственным аргументом "#developer-name"
). Совсем не одно и то же, не говоря уже о том, что в первом случае Page
ссылается на класс (заведомо прототип в JavaScript), а во втором Page
ссылается на объект, который не соответствует шаблону класс.
Самое интересное начинается, когда вы понимаете, что, поскольку класс является таким же объектом, как и любой другой объект в JavaScript, любой объект можно использовать в качестве класса, если вы знаете, как работает наследование на основе прототипов:
new (function() { this.nameInput = Selector("#developer-name"); })();
Что здесь происходит? Вы создаете новый объект с безымянной функцией в качестве конструктора объекта. Эффект абсолютно эквивалентен иному созданию объекта с new Page
, где Page
является исходным классом ES6 (ECMAScript 6 — это спецификация языка, которая добавляет синтаксис class
в JavaScript).
Вы также можете сделать это, опять же, как если бы вы определили Page
с class Page ...
:
function Page() {
this.nameInput = Selector("#developer-name");
}
var foo = new Page();
Page.prototype
будет прототипом объекта для foo
, доступным как foo.__proto__
и иным образом позволяющим вам вызывать методы экземпляра для foo
, например foo.bar()
, при условии, что вы определяете свойство bar
по крайней мере для Page.prototype
:
function Page() {
this.nameInput = Selector("#developer-name");
}
Page.prototype.bar = function() {
console.log(this.nameInput);
}
var foo = new Page();
foo.bar();
На самом деле, это то, что браузер сделал бы внутри, если бы ему пришлось интерпретировать следующий код:
class Page {
constructor() {
this.nameInput = Selector("#developer-name");
}
bar() {
console.log(this.nameInput);
}
}
Список различий между двумя последними подходами выходит за рамки моего ответа (это не то же самое, что два предложенных вами подхода), но одно отличие состоит в том, что с class Page ...
, Page
не является свойством window
в некоторых пользовательских агентах. в то время как с function Page ...
это так. Отчасти это связано с историческими причинами, но будьте уверены, что до сих пор определение конструкторов и прототипов с использованием любого из этих подходов практически одинаково, хотя я могу представить, что более умные среды выполнения JavaScript смогут лучше оптимизировать последнюю форму (потому что это атомарное объявление, а не просто последовательность выражений и утверждений).
Если вы понимаете наследование на основе прототипов в основе всего этого, все ваши вопросы по этому поводу отпадут сами собой, поскольку очень немногие фундаментальные механизмы JavaScript поддерживают 99% его особенностей. Вы также сможете оптимизировать дизайн объектов и шаблоны доступа, зная, когда выбирать классы ES6, когда нет, когда использовать объектные литералы ({ prop: value, ... }
), а когда нет, и как использовать меньше объектов между свойствами.
18.09.2019