Javascript-форум (https://javascript.ru/forum/)
-   Общие вопросы Javascript (https://javascript.ru/forum/misc/)
-   -   область видимости переменных в prototype (тупой вопрос)) (https://javascript.ru/forum/misc/15054-oblast-vidimosti-peremennykh-v-prototype-tupojj-vopros.html)

stnw 11.02.2011 12:32

область видимости переменных в prototype (тупой вопрос))
 
Добрый день!

Пытаюсь разобраться по прочтении статьи о наследовании (http://javascript.ru/tutorial/object/inheritance). Как я понял, методы и свойства объявленные в прототипе класса становятся общие для всех объектов. И в статье даже приводится пример:

В него я добавил лишь одно дополнительное свойство something. Но почему-то в другом объекте оно осталось прежним. Получается, что свойство something создалось для каждого объекта.

function Hamster() {  }
Hamster.prototype = {
    food: [],
    something: 'no',
    found: function(something) {
        this.food.push(something)
		this.something = something;
    }
}
speedy = new Hamster()
lazy = new Hamster()
 
speedy.found("apple")
speedy.found("orange")
 
alert(speedy.food.length) // 2
alert(lazy.food.length) // 2 (!??)

alert(speedy.something) // orange
alert(lazy.something) // no //почему no???


Просьба простить за глупый вопрос и наставить на путь истинный.
Заранее благодарен!

B@rmaley.e><e 11.02.2011 15:49

function Hamster() {  }
Hamster.prototype = {
    food: [],
    something: 'no',
    something1: {p:0},
    found: function(something) {
		this.food.push(something)
		this.something = something;
		this.something1 = {p:Math.random()};
    }
};
speedy = new Hamster();
lazy = new Hamster();
 
speedy.found("apple");
speedy.found("orange");
 
alert([
  'Speedy: ' + speedy.food,
  'Lazy: ' + lazy.food,
  'Prototype: ' + Hamster.prototype.food
].join('\n')); 

alert([
  'Speedy: ' + JSON.stringify(speedy.something1),
  'Lazy: ' + JSON.stringify(lazy.something1),
  'Prototype: ' + JSON.stringify(Hamster.prototype.something1)
].join('\n')); 

alert([
  'Speedy: ' + speedy.something,
  'Lazy: ' + lazy.something,
  'Prototype: ' + Hamster.prototype.something
].join('\n'));
Все дело в том, что food - массив (и, соответственно, объект). При обращении к this.food JS сначала пойдет искать свойство food у самого объекта, а потом, не найдя его, пойдет по цепочке прототипов (напоминаю, что при использовании оператора new ссылка на объект-прототип просто записывается во внутреннее свойство [[Prototype]]). В Hamster.prototype он найдет искомое значение и вернет ссылку на него. Ссылку на food из прототипа (!). Не удивительно, что изменится food в прототипе, а не объекте (в объекте свойства food, вообще говоря, нет).

Рассмотрим подробнее, почему так происходит.
1. JS "считывает" this.food.
2. Применяется оператор доступа к свойству. Мы получаем Refernce-объект с базой this (на самом деле не this, а именно тот объект, на который он ссылается) и именем свойства food.
3. Далее "считывается" .push.
4. Снова тот же оператор, и мы получаем в качестве базы значение предыдущего Reference-объекта и именем свойства push. Теперь подробнее о значении Reference-объекта: в ходе выполнения [[Get]]
JS не находит значения food как собственного свойства объекта и идет в объект-прототип [[Prototype]]. Соответствено, значением Reference-объекта будет объект food прототипа, т.е. [[Prototype]].food.
5. Считываются и обрабатываются аргументы метода, это нам сейчас не важно.
6. Вызывается метод. В качестве this берется база нового Reference-объекта (см. шаг 6). А эта самая база указывает на [[Prototype]].food. Соответственно, и изменения будут проведены над этим объектом.

Казалось бы, почему тогда поведение something отличается? Тут все дело в том, что мы не обращаемся к свойству

1. JS "считывает" this.something.
2. Применяется оператор доступа к свойству. Мы получаем Refernce-объект с базой this и именем свойства something.
3. Далее "считывается" оператор присваивания.
4. В ходе записи значения в Reference вызывается метод [[Put]] объекта, который лежит у нас в базе (а это - тот самый, на кого указывает this), и этот метод записывает присваиваемое значение свойству самого объекта, а не его прототипа. По-сути, его совсем не волнует, что такое something и откуда оно пришло. Он просто запишет нужное свойство. В случае с массивом нам пришлось обратиться к значению this.food, когда мы использовали оператор обращения к свойству.

stnw 11.02.2011 17:53

Большое спасибо за подробный ответ, вроде все становится на свои места.

Для закрепления еще вопрос: я правильно понимаю, что если мы используем свойство property как константу, то ее выгодней держать в прототипе (она не будет занимать место в памяти, а каждый раз будет браться из прототипа)? Если же производятся манипуляции с этой переменной в методах класса, то следующие записи будут аналогичны (т.к. свойство все равно каждый раз будет обновляться в самом объекте).

function Class() {
this.property = 0;
}

function Class() {}
Class.prototype.property = 0;

Спасибо заранее.

B@rmaley.e><e 11.02.2011 19:25

С точки зрения маниакальной экономии памяти держать в прототипе выгоднее. Но в JS редко приходится сталкиваться с таким большим числом объектов, чтобы хранение данных в объекте вместо прототипа сильно сказывалось на потреблении памяти.

Записи аналогичны (функционально, конечно) до тех пор, пока Вы не будете изменять то, что находится в прототипе (как это происходило с массивом food).


Часовой пояс GMT +3, время: 20:08.