область видимости переменных в 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??? Просьба простить за глупый вопрос и наставить на путь истинный. Заранее благодарен! |
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, когда мы использовали оператор обращения к свойству. |
Большое спасибо за подробный ответ, вроде все становится на свои места.
Для закрепления еще вопрос: я правильно понимаю, что если мы используем свойство property как константу, то ее выгодней держать в прототипе (она не будет занимать место в памяти, а каждый раз будет браться из прототипа)? Если же производятся манипуляции с этой переменной в методах класса, то следующие записи будут аналогичны (т.к. свойство все равно каждый раз будет обновляться в самом объекте). function Class() { this.property = 0; } function Class() {} Class.prototype.property = 0; Спасибо заранее. |
С точки зрения маниакальной экономии памяти держать в прототипе выгоднее. Но в JS редко приходится сталкиваться с таким большим числом объектов, чтобы хранение данных в объекте вместо прототипа сильно сказывалось на потреблении памяти.
Записи аналогичны (функционально, конечно) до тех пор, пока Вы не будете изменять то, что находится в прототипе (как это происходило с массивом food). |
Часовой пояс GMT +3, время: 23:13. |