Создание объектов через "new"

Обычный синтаксис {...} позволяет создать один объект. Но зачастую нужно создать много однотипных объектов.

Для этого используют «функции-конструкторы», запуская их при помощи специального оператора new.

Конструктор

Конструктором становится любая функция, вызванная через new.

Например:

function Animal(name) {
  this.name = name;
  this.canWalk = true;
}

var animal = new Animal("ёжик");

Заметим, что, технически, любая функция может быть использована как конструктор. То есть, любую функцию можно вызвать при помощи new. Как-то особым образом указывать, что она – конструктор – не надо.

Но, чтобы выделить функции, задуманные как конструкторы, их называют с большой буквы: Animal, а не animal.

Детальнее – функция, запущенная через new, делает следующее:

  1. Создаётся новый пустой объект.
  2. Ключевое слово this получает ссылку на этот объект.
  3. Функция выполняется. Как правило, она модифицирует this (т.е. этот новый объект), добавляет методы, свойства.
  4. Возвращается this.

В результате вызова new Animal("ёжик"); получаем такой объект:

animal = {
  name: "ёжик",
  canWalk: true
}

Иными словами, при вызове new Animal происходит что-то в таком духе (первая и последняя строка – это то, что делает интерпретатор):

function Animal(name) {
  // this = {};

  // в this пишем свойства, методы
  this.name = name;
  this.canWalk = true;

  // return this;
}

Теперь многократными вызовами new Animal с разными параметрами мы можем создать столько объектов, сколько нужно. Поэтому такую функцию и называют конструктором – она предназначена для «конструирования» объектов.

new function() { … }

Иногда функцию-конструктор объявляют и тут же используют, вот так:

var animal = new function() {
  this.name = "Васька";
  this.canWalk = true;
};

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

var animal = {
  name: "Васька",
  canWalk: true
}

…Но обычный синтаксис {...} не подходит, когда при создании свойств объекта нужны более сложные вычисления. Их можно проделать в функции-конструкторе и записать результат в this.

Правила обработки return

Как правило, конструкторы ничего не возвращают. Их задача – записать всё, что нужно, в this, который автоматически станет результатом.

Но если явный вызов return всё же есть, то применяется простое правило:

  • При вызове return с объектом, будет возвращён он, а не this.
  • При вызове return с примитивным значением, оно будет отброшено.

Иными словами, вызов return с объектом вернёт объект, а с чем угодно, кроме объекта – возвратит, как обычно, this.

Например, возврат объекта:

function BigAnimal() {

  this.name = "Мышь";

  return { name: "Годзилла" };  // <-- возвратим объект
}

alert( new BigAnimal().name );  // Годзилла, получили объект вместо this

А вот пример с возвратом строки:

function BigAnimal() {

  this.name = "Мышь";

  return "Годзилла"; // <-- возвратим примитив
}

alert( new BigAnimal().name ); // Мышь, получили this (а Годзилла пропал)

Эта особенность работы new прописана в стандарте, но используется она весьма редко.

Можно без скобок

Кстати, при вызове new без аргументов скобки можно не ставить:

var animal = new BigAnimal; // <-- без скобок
// то же самое что
var animal = new BigAnimal();

Не сказать, что выбрасывание скобок – «хороший стиль», но такой синтаксис допустим стандартом.

Создание методов в конструкторе

Использование функций для создания объекта дает большую гибкость. Можно передавать конструктору параметры, определяющие как его создавать, и он будет «клепать» объекты заданным образом.

Добавим в создаваемый объект ещё и метод.

Например, new User(name) создает объект с заданным значением свойства name и методом sayHi:

function User(name) {
  this.name = name;

  this.sayHi = function() {
    alert( "Моё имя: " + this.name );
  };
}

var ivan = new User("Иван");

ivan.sayHi(); // Моё имя: Иван

/*
ivan = {
   name: "Иван",
   sayHi: функция
}
*/

Локальные переменные

В функции-конструкторе бывает удобно объявить вспомогательные локальные переменные и вложенные функции, которые будут видны только внутри:

function User(firstName, lastName) {
  // вспомогательная переменная
  var phrase = "Привет";

  //  вспомогательная вложенная функция
  function getFullName() {
      return firstName + " " + lastName;
    }

  this.sayHi = function() {
    alert( phrase + ", " + getFullName() ); // использование
  };
}

var vasya = new User("Вася", "Петров");
vasya.sayHi(); // Привет, Вася Петров

Мы уже говорили об этом подходе ранее, в главе Локальные переменные для объекта.

Те функции и данные, которые должны быть доступны для внешнего кода, мы пишем в this – и к ним можно будет обращаться, как например vasya.sayHi(), а вспомогательные, которые нужны только внутри самого объекта, сохраняем в локальной области видимости.

Итого

Объекты могут быть созданы при помощи функций-конструкторов:

  • Любая функция может быть вызвана с new, при этом она получает новый пустой объект в качестве this, в который она добавляет свойства. Если функция не решит возвратить свой объект, то её результатом будет this.
  • Функции, которые предназначены для создания объектов, называются конструкторами. Их названия пишут с большой буквы, чтобы отличать от обычных.

Задачи

важность: 2

Возможны ли такие функции A и B в примере ниже, что соответствующие объекты a,b равны (см. код ниже)?

function A() { ... }
function B() { ... }

var a = new A;
var b = new B;

alert( a == b ); // true

Если да – приведите пример кода с такими функциями.

Да, возможны.

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

Например, они могут вернуть один и тот же объект obj, определённый снаружи:

var obj = {};

function A() { return obj; }
function B() { return obj; }

var a = new A;
var b = new B;

alert( a == b ); // true
важность: 5

Напишите функцию-конструктор Calculator, которая создает объект с тремя методами:

  • Метод read() запрашивает два значения при помощи prompt и запоминает их в свойствах объекта.
  • Метод sum() возвращает сумму запомненных свойств.
  • Метод mul() возвращает произведение запомненных свойств.

Пример использования:

var calculator = new Calculator();
calculator.read();

alert( "Сумма=" + calculator.sum() );
alert( "Произведение=" + calculator.mul() );

Запустить демо

Открыть песочницу с тестами для задачи.

function Calculator() {

  this.read = function() {
    this.a = +prompt('a?', 0);
    this.b = +prompt('b?', 0);
  };

  this.sum = function() {
    return this.a + this.b;
  };

  this.mul = function() {
    return this.a * this.b;
  };
}

var calculator = new Calculator();
calculator.read();

alert( "Сумма=" + calculator.sum() );
alert( "Произведение=" + calculator.mul() );

Открыть решение с тестами в песочнице.

важность: 5

Напишите функцию-конструктор Accumulator(startingValue). Объекты, которые она создает, должны хранить текущую сумму и прибавлять к ней то, что вводит посетитель.

Более формально, объект должен:

  • Хранить текущее значение в своём свойстве value. Начальное значение свойства value ставится конструктором равным startingValue.
  • Метод read() вызывает prompt, принимает число и прибавляет его к свойству value.

Таким образом, свойство value является текущей суммой всего, что ввел посетитель при вызовах метода read(), с учетом начального значения startingValue.

Ниже вы можете посмотреть работу кода:

var accumulator = new Accumulator(1); // начальное значение 1
accumulator.read(); // прибавит ввод prompt к текущему значению
accumulator.read(); // прибавит ввод prompt к текущему значению
alert( accumulator.value ); // выведет текущее значение

Запустить демо

Открыть песочницу с тестами для задачи.

function Accumulator(startingValue) {
  this.value = startingValue;

  this.read = function() {
    this.value += +prompt('Сколько добавлять будем?', 0);
  };

}

var accumulator = new Accumulator(1);
accumulator.read();
accumulator.read();
alert( accumulator.value );

Открыть решение с тестами в песочнице.

важность: 5

Напишите конструктор Calculator, который создаёт расширяемые объекты-калькуляторы.

Эта задача состоит из двух частей, которые можно решать одна за другой.

  1. Первый шаг задачи: вызов calculate(str) принимает строку, например «1 + 2», с жёстко заданным форматом «ЧИСЛО операция ЧИСЛО» (по одному пробелу вокруг операции), и возвращает результат. Понимает плюс + и минус -.

    Пример использования:

    var calc = new Calculator;
    
    alert( calc.calculate("3 + 7") ); // 10
  2. Второй шаг – добавить калькулятору метод addMethod(name, func), который учит калькулятор новой операции. Он получает имя операции name и функцию от двух аргументов func(a,b), которая должна её реализовывать.

    Например, добавим операции умножить *, поделить / и возвести в степень **:

    var powerCalc = new Calculator;
    powerCalc.addMethod("*", function(a, b) {
      return a * b;
    });
    powerCalc.addMethod("/", function(a, b) {
      return a / b;
    });
    powerCalc.addMethod("**", function(a, b) {
      return Math.pow(a, b);
    });
    
    var result = powerCalc.calculate("2 ** 3");
    alert( result ); // 8
  • Поддержка скобок и сложных математических выражений в этой задаче не требуется.
  • Числа и операции могут состоять из нескольких символов. Между ними ровно один пробел.
  • Предусмотрите обработку ошибок. Какая она должна быть – решите сами.

Открыть песочницу с тестами для задачи.

function Calculator() {

  var methods = {
    "-": function(a, b) {
      return a - b;
    },
    "+": function(a, b) {
      return a + b;
    }
  };

  this.calculate = function(str) {

    var split = str.split(' '),
      a = +split[0],
      op = split[1],
      b = +split[2]

    if (!methods[op] || isNaN(a) || isNaN(b)) {
      return NaN;
    }

    return methods[op](a, b);
  }

  this.addMethod = function(name, func) {
    methods[name] = func;
  };
}

var calc = new Calculator;

calc.addMethod("*", function(a, b) {
  return a * b;
});
calc.addMethod("/", function(a, b) {
  return a / b;
});
calc.addMethod("**", function(a, b) {
  return Math.pow(a, b);
});

var result = calc.calculate("2 ** 3");
alert( result ); // 8
  • Обратите внимание на хранение методов. Они просто добавляются к внутреннему объекту.
  • Все проверки и преобразование к числу производятся в методе calculate. В дальнейшем он может быть расширен для поддержки более сложных выражений.

Открыть решение с тестами в песочнице.

Карта учебника

Комментарии

перед тем как писать…
  • Приветствуются комментарии, содержащие дополнения и вопросы по статье, и ответы на них.
  • Для одной строки кода используйте тег <code>, для нескольких строк кода — тег <pre>, если больше 10 строк — ссылку на песочницу (plnkr, JSBin, codepen…)
  • Если что-то непонятно в статье — пишите, что именно и с какого места.