Конспект JS-course

Задачи

Задача 1

При выполнении этого кода вызов rabbit.eat() запишет в объект свойство full.

Вопрос — в какой объект: в rabbit или animal?

var animal = { };
var rabbit = { };

rabbit.__proto__ = animal;

animal.eat = function() {
    this.full = true;
};

rabbit.eat();

Решение:

Ответ: свойство будет записано в rabbit.

Если коротко — то потому что this будет указывать на rabbit, а прототип при записи не используется.

Если в деталях — посмотрим как выполняется rabbit.eat():

  1. Интерпретатор ищет rabbit.eat, чтобы его вызвать. Но свойство eat отсутствует в объекте rabbit, поэтому он идет по ссылке rabbit.__proto__ и находит это свойство там.

  2. Функция eat запускается. Контекст ставится равным объекту перед точкой, т.е. this = rabbit. Итак — получается, что команда this.full = true устанавливает свойство full в самом объекте rabbit. Итог:

Задача 2

Какие значения будут выводиться в коде ниже?

var animal = { jumps: null };
var rabbit = { jumps: true };

rabbit.__proto__ = animal;

alert( rabbit.jumps ); // ? (1)

delete rabbit.jumps;
alert( rabbit.jumps ); // ? (2)

delete animal.jumps;
alert( rabbit.jumps);  // ? (3)

Итого три вопроса.

Решение

  1. true, свойство взято из rabbit.
  2. null, свойство взято из animal.
  3. undefined, свойства больше нет.

Задача 3

Есть объекты:

var head = {
  glasses: 1
};

var table = {
  pen: 3
};

var bed = {
  sheet: 1,
  pillow: 2
};

var pockets = {
  money: 2000
};

Задание состоит из двух частей:

  1. Присвойте объектам ссылки __proto__ так, чтобы любой поиск чего-либо шёл по алгоритму pockets -> bed -> table -> head. То есть pockets.pen == 3, bed.glasses == 1, но table.money == undefined.
  2. После этого ответьте на вопрос, как быстрее искать glasses: обращением к pockets.glasses или head.glasses? Попробуйте протестировать.

Решение:

1) Расставим __proto__:

var head = {
  glasses: 1
};

var table = {
  pen: 3
};
table.__proto__ = head;

var bed = {
  sheet: 1,
  pillow: 2
};
bed.__proto__ = table;

var pockets = {
  money: 2000
};
pockets.__proto__ = bed;

alert( pockets.pen ); // 3
alert( bed.glasses ); // 1
alert( table.money ); // undefined

2) В современных браузерах, с точки зрения производительности, нет разницы, брать свойство из объекта или прототипа. Они запоминают, где было найдено свойство и в следующий раз при запросе, к примеру, pockets.glasses начнут искать сразу в прототипе head.

Задача 4

В примерах ниже производятся различные действия с prototype.

Каковы будут результаты выполнения? Почему?

function Rabbit() { }
Rabbit.prototype = { eats: true };

var rabbit = new Rabbit();

Rabbit.prototype = {};

alert(rabbit.eats);

А если код будет такой? (заменена одна строка):

function Rabbit(name) { }
Rabbit.prototype = { eats: true };

var rabbit = new Rabbit();

Rabbit.prototype.eats = false; // (*)

alert(rabbit.eats);

А такой? (заменена одна строка)

function Rabbit(name) { }
Rabbit.prototype = { eats: true };

var rabbit = new Rabbit();

delete Rabbit.prototype.eats; // (*)

alert(rabbit.eats);

А если бы в последнем коде вместо строки (*) было delete rabbit.eats?

Итого 4 вопроса.

Решение

  1. Результат: true. Свойство prototype всего лишь задаёт __proto__ у новых объектов. Так что его изменение не повлияет на rabbit.__proto__. Свойство eats будет получено из прототипа.
  2. Результат: false. Свойство Rabbit.prototype и rabbit.__proto__ указывают на один и тот же объект. В данном случае изменения вносятся в сам объект.
  3. Результат: undefined. Удаление осуществляется из самого прототипа, поэтому свойство rabbit.eats больше взять неоткуда.
  4. Результат был бы true, так как delete rabbit.eats попыталось бы удалить eats из rabbit, где его и так нет. А чтение в alert прошло бы из прототипа.

Задача 5

Создадим новый объект, вот такой:

function Rabbit() { }
Rabbit.prototype.test = function() { alert(this); }

var rabbit = new Rabbit();

Есть ли разница между вызовами:

rabbit.test();
rabbit.__proto__.test();
Rabbit.prototype.test();
Object.getPrototypeOf(rabbit).test();

Какие из этих вызовов идентичны в браузере IE9+? А в Chrome?

Решение

  1. Первый вызов ставит this == rabbit, остальные ставят this равным прототипу, следуя правилу «this — объект перед точкой». При этом второй вызов не поддерживается в IE, т.к. свойство __proto__ — нестандартное. А третий и четвёртый — идентичны. В Chrome идентичны три последних вызова.

Задача 6

Вы — руководитель команды, которая разрабатывает игру, хомяковую ферму. Один из программистов получил задание создать класс «хомяк» (англ - "Hamster").

Объекты-хомяки должны иметь массив food для хранения еды и метод found, который добавляет к нему.

Ниже — его решение. При создании двух хомяков, если поел один — почему-то сытым становится и второй тоже.

В чём дело? Как поправить?

function Hamster() {  }

Hamster.prototype.food = [ ]; // пустой "живот"

Hamster.prototype.found = function(something) {
  this.food.push(something);
};

// Создаём двух хомяков и кормим первого
speedy = new Hamster();
lazy = new Hamster();

speedy.found("яблоко");
speedy.found("орех");

alert(speedy.food.length); // 2
alert(lazy.food.length);   // 2 (!??)

Решение

Давайте подробнее разберем происходящее при вызове speedy.found("яблоко"):

Интерпретатор ищет свойство found в speedy. Но speedy — пустой объект, т.к. new Hamster ничего не делает с this . Интерпретатор идёт по ссылке speedy.__proto__ (==Hamster.prototype) и находят там метод found, запускает его. Значение this устанавливается в объект перед точкой, т.е. в speedy. Для выполнения this.food.push() нужно найти свойство this.food. Оно отсутствует в speedy, но есть в speedy.__proto__. Значение "яблоко" добавляется в speedy.__proto__.food. У всех хомяков общий живот! Или, в терминах JavaScript, свойство food изменяется в прототипе, который является общим для всех объектов-хомяков.

Заметим, что этой проблемы не было бы при простом присваивании:

this.food = something;

В этом случае значение записалось бы в сам объект, без поиска found в прототипе.

Проблема возникает только со свойствами-объектами в прототипе.

Для исправления проблемы нужно дать каждому хомяку свой живот. Это можно сделать, присвоив его в конструкторе.

function Hamster() {
  this.food = [];
}

Hamster.prototype.found = function(something) {
  this.food.push(something);
};

speedy = new Hamster();
lazy = new Hamster();

speedy.found("яблоко");
speedy.found("орех");

alert(speedy.food.length) // 2
alert(lazy.food.length) // 0(!)

Теперь всё в порядке. У каждого хомяка — свой живот.