Введение в ООП: Конструктор

Всем привет! Прошу помочь в нахождении и исправлении ошибки в моём коде по задаче.
Задание:

Мой код:
Файл Point

const Point = (x, y) => {
  return {
    x,
    y,
  };
};

Файл Segment

const Segment = (beginPoint, endPoint) => {
  return {
    beginPoint,
    endPoint,
    getBeginPoint() {
      return this.beginPoint;
    },
    getEndPoint() {
      return this.endPoint;
    },
  };
};

Файл Solution

const reverse = (segment) => {
  { segment.getEndPoint, segment.getBeginPoint };
}

Вывод теста:(Не знаю, в помощь ли он, но на всякий случай, оставлю здесь)

FAIL  __tests__/solution.test.js

  ✕ reverse (4ms)


  ● reverse

    TypeError: _Point.default is not a constructor

       6 | 
       7 | test('reverse', () => {
    >  8 |   const point1 = new Point(1, 10);
         |                  ^
       9 |   const point2 = new Point(11, -3);
      10 |   const segment = new Segment(point1, point2);
      11 |   const reversedSegment = reverse(segment);

      at Object.<anonymous> (__tests__/solution.test.js:8:18)


Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        1.367s, estimated 2s
Ran all test suites.

Конструктор и методы класса описываются так:

function Point(x, y) {
	this.x = x
	this.y = y
}

Point.prototype.getStr = function () {
	return 'x:' + this.x + ',y:' + this.y 
}

const p = new Point(10, 20)
console.log(p.getStr())

Тебе нужно переделать оба Point и Segment.

1 лайк

Я как-то отключился и не добавил “современный” синтаксис через es6 классы. Конструктор и методы:

class Point {
	constructor(x,y) {
		this.x = x
		this.y = y
	}

	getStr() {
		return 'x:' + this.x + ',y:' + this.y
	}
}

const p = new Point(10, 20)
console.log(p.getStr())

1 лайк

мне нужно сеттер написать, а на такой синтасис, редактор ругается:

 get getBeginPoint() {
      return this.beginPoint;
    },

Полный код:

function Segment (beginPoint, endPoint){
  
    this.beginPoint = beginPoint,
    this.endPoint = endPoint,
    get getBeginPoint() {
      return this.beginPoint;
    },
    get getEndPoint() {
      return this.endPoint;
    },
  
};
  1. Покажи весь код
  2. Покажи текст ошибки
  3. Скорее всего запятая лишняя

Ты мешаешь синтаксис создания класса через функцию-конструктор и через class. Пересмотри мои примеры выше

Беру синтаксис из теории:

Конструктор

Приложения на JavaScript, во время своей работы, создают и удаляют множество объектов. Иногда эти объекты совсем разные, а иногда они относятся к одному понятию, но отличаются данными. Когда речь идет про понятия предметной области (или как говорят сущности), то важно иметь абстракцию, которая скроет от нас структуру этого объекта.

Возьмем понятие “компания” и построим абстракцию вокруг него без использования инкапсуляции:

// Реальное устройство будет значительно сложнее
// файл: company.js

// Конструктор (в общем смысле этого слова)
const make = (name, website) => {
  return { name, website };
}

// Селекторы
const getName = (company) => company.name;
const getWebsite = (company) => company.website;

Теперь использование:

import { make, getName } from './company.js';

const company = make('Hexlet', 'https://hexlet.io');
console.log(getName(company)); // Hexlet

Такая абстракция упрощает работу с компаниями (особенно при изменениях структуры), прячет детали реализации и делает код более “человечным”. Попробуем сделать то же самое используя инкапсуляцию:

// Реальное устройство будет значительно сложнее
// файл: company.js

const make = (name, website) => {
  return {
    name,
    website,
    getName() {
      return this.name;
    },
    getWebsite() {
      return this.website;
    },
  };
}

И использование:

import { make } from './company.js';

const company = make('Hexlet', 'https://hexlet.io');
console.log(company.getName()); // Hexlet

Здесь мы видим несколько удобных моментов по сравнению с вариантом на функциях:

  • Нам больше не надо импортировать getName , он уже содержится внутри компании.
  • Можно пользоваться автодополнением кода.

Но вместе с плюсами пришли и минусы. Посмотрите еще раз внимательно на код конструктора. Каждый его вызов возвращает новый объект и это ожидаемое поведение, но чего мы точно не хотим, так это создания методов на каждый вызов конструктора (а они будут создаваться при каждом создании объекта). Методы, в отличие от обычных данных, не меняются. Нет никакого смысла создавать их на каждый вызов заново, расходуя память и процессорное время.

Перепишем наш пример, избежав постоянного создания методов:

// Не забываем что нам нужны обычные, а не стрелочные функции!

function getName() {
  return this.name;
};

function getWebsite() {
  return this.website;
};

// С точки зрения использования ничего не поменялось, но зато перестали копироваться функции.
const make = (name, website) => {
  return {
    name,
    website,
    getName,
    getWebsite,
  };
}

Все описанные выше способы создания объектов имеют право на существование и используются в реальной жизни, но в JavaScript есть встроенная поддержка генерации объектов. Перепишем наш пример и разберем его.

Оператор new

// Такую функцию принято называть конструктором (хотя технически это обычная функция с контекстом)
// Конструкторы пишутся с заглавной буквы
function Company(name, website) {
  // Такую функцию нельзя вызвать просто так, потому что тут есть this
  this.name = name;
  this.website = website;
  // Методы по прежнему определены снаружи как обычные функции
  this.getName = getName;
  this.getWebSite = getWebsite;
}

Теперь использование:

const company = new Company('Hexlet', 'https://hexlet.io');
console.log(company.getName());

Самое интересное в этом примере – оператор new (как и все остальное в js, он работает не так как new в других языках). Фактически он создает объект, устанавливает его как контекст во время вызова конструктора (в данном случае Company ) и возвращает созданный объект. Именно поэтому сам конструктор ничего не возвращает (хотя может, но это другой разговор), а внутри константы company оказывается нужный нам объект.

// Упрощенная иллюстрация работы new внутри интерпретатора при таком вызове:
// new Company();

const obj = {};
Company.bind(obj)(name, website) // этот вызов просто наполнил this (равный obj) нужными данными
return obj;

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

Все типы данных в JavaScript, которые могут быть представлены объектами (или являются объектами внутри себя, например функции), имеют встроенные конструкторы. Иногда они заменяют специальный синтаксис создания данных (как в случае с массивами), а иногда это единственный способ создать данные этого типа (как в случае с датами):

// Специальный синтаксис создания массивов
// Массивы это объекты, вспомните свойство length
const numbers = [10, 3, -3, 0]; // литерал

// Объектный способ создания через конструктор
// Результат ниже эквивалентен тому что происходит выше
const numbers = new Array(10, 3, -3, 0);

// У дат нет литералов, они создаются как объекты
const date = new Date('December 17, 1995 03:24:00');
// У дат очень много методов
date.getMonth(); // 11

// Так можно создавать даже функции
// Последний аргумент это тело, все предыдущие – аргументы
const sum = new Function('a', 'b', 'return a + b');
sum(2, 6); // 8

P.S. Классы пока не проходили

Нет простого синтаксиса объявления геттеров с функциями-конструкторами.

Используй объявление class чтобы мочь объявить

get getEndPoint() {
      return this.endPoint;
    }

Решение учителя:
Point

function getX() {
  return this.x;
}

function getY() {
  return this.y;
}

function Point(x, y) {
  this.x = x;
  this.y = y;
  this.getX = getX;
  this.getY = getY;
}

Segment

function getBeginPoint() {
  return this.beginPoint;
}

function getEndPoint() {
  return this.endPoint;
}

function Segment(beginPoint, endPoint) {
  this.beginPoint = beginPoint;
  this.endPoint = endPoint;
  this.getBeginPoint = getBeginPoint;
  this.getEndPoint = getEndPoint;
}

Point

export default (segment) => {
  const beginPoint = segment.getBeginPoint();
  const endPoint = segment.getEndPoint();
  const newEndPoint = new Point(beginPoint.getX(), beginPoint.getY());
  const newBeginPoint = new Point(endPoint.getX(), endPoint.getY());

  return new Segment(newBeginPoint, newEndPoint);
};

Идиоматически было бы описать методы в прототипе конструктора.

function Point(x, y) {
	this.x = x;
	this.y = y;
}

Point.prototype.getX = function () {
	return this.x;
}

Point.prototype.getY = function () {
	return this.y;
}

Или классом.

class Point {
	constructor(x, y) {
		this.x = x;
		this.y = y;
	}

	getX() {
		return this.x;
	}

	getY() {
		return this.y;
	}
}

Объявлять функции которые обращаются к this вне класса это излишнее усложнение для читающего и понимающего код. Плюс так нормальные люди не пишут. Нужно подсказать кодом о том чем будет этот самый this.

Остальное норм.

Наверное, но прототипы изучаем со следующего урока, а классы тем более ещё не смотрели))
Учительское решение исходит из того, что на данный момент мы проходим!

Это известная тема. Значит будт осторожнее в восприятии того что какие подходы брать и использовать, а какие - временные конструкции.

странно что в задании просят создать именно функцию-конструктор (подход который лет 5-6 назад минимум был еще как-то актуальный), при этом во всю используют const (это уже es6)

я бы использовал современный синтаксис:

class Point {
	constructor(x, y) {
		this.x = x;
		this.y = y;
	}
}

class Segment {
	constructor(beginPoint, endPoint) {
		this.beginPoint = beginPoint;
		this.endPoint = endPoint;
	}

	getBeginPoint() {
		return this.beginPoint;
	}

	getEndPoint() {
		return this.endPoint;
	}
}

function reverse(segment) {
	const firstPoint = copyObject(segment.getEndPoint());
	const lastPoint = copyObject(segment.getBeginPoint());

	return new Segment(firstPoint, lastPoint);
}

function copyObject(obj) {
	return Object.assign({}, obj);
}

// ================================================================== //
const s = new Segment(new Point(1,1), new Point(10, 10));
console.log('initial segment:')
console.log(s);

const reversedSegment = reverse(s);
console.log('reversed segment:');
console.log(reversedSegment);

// check links
console.log(s.getBeginPoint() === reversedSegment.getEndPoint());
console.log(s.getEndPoint() === reversedSegment.getBeginPoint());

разве что это задание именно на прототипы

1 лайк

output

Павел, в этой части урока, прототипа и классов ещё нет! До них ещё не дошли. Именно поэтому не использовали тот синтаксис, что вы написали!

в принципе до тех пор пока не дошли до прототипного наследования особой боли небудет, просто заменить constructor функцией, а методы вынести на prototype. а вот когда надо будет населование реализовать или свой метод extend написать… тогда новый синтаксис с class A extends B в разы приятнее покажется))