Arhn - архитектура программирования

В чем разница между литералом объекта и классом со значениями в конструкторе в javascript?

Я работал над сквозным тестом в testcafe, и в их документации я нашел следующее решение для модели страницы:

class Page {
    constructor () {
        this.nameInput = Selector('#developer-name');
    }
}

export default new Page();

Я проводил некоторые исследования, и я не могу понять, почему это не разрешено с помощью литерала объекта:

export const Page = {
    nameInput: Selector('#developer-name');
}

Каковы последствия использования каждого из них?


  • Поскольку Page не имеет методов, класс не имеет никакого смысла. Даже если бы у страницы были методы, было бы проще определить их в литерале экспортируемого объекта, чем определить класс, поскольку на класс ссылаются только один раз. 18.09.2019
  • Ну, вы могли бы сделать variable instanceof Page с первым, но не со вторым. Однако, учитывая, что вам нужен только один экземпляр, я не уверен, насколько это будет полезно. 18.09.2019
  • Класс позволит вам создать несколько экземпляров Page (хотя в этом примере создается и отображается только один), но литерал объекта предоставляет только один экземпляр. У вас все еще может быть несколько экземпляров с подходом литерала объекта, если вы измените Page на функцию, которая возвращает литерал объекта (например, конструктор DIY): export const Page = () => ({nameInput: Selector('developer-name')}); 18.09.2019

Ответы:


1

Разница существенная, но по сути оба являются объектами 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

2

Объявив его как класс, вы можете позже определить, к какому типу объекта он относится, с помощью .constructor.name:

class Page {
  constructor () {
    this.nameInput = "something";
  }
  // No comma
  anotherMethod() {
  }
}

const pageClass = new Page();

const pageLiteral = {
  nameInput: "something"
  , // must have a comma
   anotherMethod() {
  }   
}

console.log("Name of constructor for class: ", pageClass.constructor.name); // Page
console.log("Name of constructor for literal: ", pageLiteral.constructor.name); // Object

18.09.2019

3

Классы можно рассматривать как план, в конце концов они оба предоставляют объект. Но, как следует из названия литералов объекта, вы буквально создаете его тут же с этим «буквальным» синтаксисом. Однако класс, который мы будем использовать для создания новых экземпляров из 1 базовой схемы.

let x = { myProp: undefined }
let y = { myProp: undefined }

x.myProp = "test";
y.myProp // undefined

Здесь мы видим, что делаем два отдельных экземпляра, но нам придется повторить код.

class X { }

let x = new X();
let y = new X();

Классу не нужно повторять код, так как он весь инкапсулирован в идею того, каким должен быть X, в плане.

Как и выше [в буквальном смысле], у нас есть два отдельных экземпляра, но это чище, читабельнее, и любое изменение, которое мы хотим внести в каждый экземпляр этого объекта «X», теперь можно изменить просто в классе.

Существует множество других преимуществ и даже парадигма, посвященная объектно-ориентированному программированию. Подробнее читайте здесь: https://www.internalpointers.com/post/object-literals-vs-constructors-javascript

Чтобы перейти к вопросу конструктора... В других языках у нас есть поля. Я считаю, что когда вы назначаете поле в конструкторе, он просто создает скрытое поле (я говорю скрытое, потому что JavaScript основан на прототипах, а синтаксис класса — это синтаксический сахар, облегчающий написание прототипов для программистов, знакомых с синтаксисом классов на других языках). ).

Вот пример на С#.

public class X{
   private int y;

   X() {
      this.y = 5;
   }
}

Назначать поля в конструкторе на других языках скорее принято, поэтому я предполагаю, что это как-то связано с JavaScript.

Надеюсь это поможет.

18.09.2019
Новые материалы

Коллекции публикаций по глубокому обучению
Последние пару месяцев я создавал коллекции последних академических публикаций по различным подполям глубокого обучения в моем блоге https://amundtveit.com - эта публикация дает обзор 25..

Представляем: Pepita
Фреймворк JavaScript с открытым исходным кодом Я знаю, что недостатка в фреймворках JavaScript нет. Но я просто не мог остановиться. Я хотел написать что-то сам, со своими собственными..

Советы по коду Laravel #2
1-) Найти // You can specify the columns you need // in when you use the find method on a model User::find(‘id’, [‘email’,’name’]); // You can increment or decrement // a field in..

Работа с временными рядами спутниковых изображений, часть 3 (аналитика данных)
Анализ временных рядов спутниковых изображений для данных наблюдений за большой Землей (arXiv) Автор: Рольф Симоэс , Жильберто Камара , Жильберто Кейрос , Фелипе Соуза , Педро Р. Андраде ,..

3 способа решить квадратное уравнение (3-й мой любимый) -
1. Методом факторизации — 2. Используя квадратичную формулу — 3. Заполнив квадрат — Давайте поймем это, решив это простое уравнение: Мы пытаемся сделать LHS,..

Создание VR-миров с A-Frame
Виртуальная реальность (и дополненная реальность) стали главными модными терминами в образовательных технологиях. С недорогими VR-гарнитурами, такими как Google Cardboard , и использованием..

Демистификация рекурсии
КОДЕКС Демистификация рекурсии Упрощенная концепция ошеломляющей О чем весь этот шум? Рекурсия, кажется, единственная тема, от которой у каждого начинающего студента-информатика..