Зачем объявлять свойства прототипа для переменных экземпляра в JavaScript
Я пытаюсь обойти это черное искусство под названием JavaScript - и, надо признать, очень взволнован. Я рассматривал примеры кода, в основном из easeljs, так как именно это я буду использовать в основном. И я немного смущен.
Я (думаю, я) понимаю разницу между использованием прототипа для функций или свойств, которые являются переменными класса и использованием this.someProp для переменных "instance" (да, я понимаю, что в JavaScript нет классов).
Код, на который я смотрел, и использую в качестве шаблонов для своего собственного кода, объявляю переменные прототипа и затем ссылаюсь на них с помощью этого i.e.
В конструкторе: this.name = name;
Затем объявление: Object.prototype.name;
И позже, this.name = "Freddy";
Это внутри функций, называемых "new", поэтому в этом случае, как я понимаю, 'this' относится к текущему объекту. Что меня озадачивает, что делает объявление прототипа, и почему мы используем его, например, переменные?
Спасибо, Дэйв
ОК, немного разъяснений. В следующем коде я не вижу того, чего добивается объявление радиуса прототипа:
(function(){
// constructor
function MyCircle(radius){
this.radius = radius;
}
MyCircle.prototype.radius;
this.area = function(){
return 3.14*this.radius*this.radius;
};
window.MyCircle = MyCircle;
}());
Ответы
Ответ 1
Значение в прототипе имеет ключевое поведение, отличное от свойства, установленного непосредственно на экземпляре. Попробуйте следующее:
// Create a constructor
function A() {}
// Add a prototype property
A.prototype.name = "Freddy";
// Create two object instances from
// the constructor
var a = new A();
var b = new A();
// Both instances have the property
// that we created on the prototype
console.log(a.name); // Freddy
console.log(b.name); // Freddy
// Now change the property on the
// prototype
A.prototype.name = "George";
// Both instances inherit the change.
// Really they are just reading the
// same property from the prototype
// rather than their own property
console.log(a.name); // George
console.log(b.name); // George
Это невозможно без прототипического наследования.
Вы можете проверить, является ли свойство свойством instance или свойством prototype с помощью метода hasOwnProperty
.
console.log(a.hasOwnProperty("name")); // false
Экземпляр может переопределить значение prototype
.
b.name = "Chris";
console.log(b.hasOwnProperty("name")); // true
console.log(a.name); // George
console.log(b.name); // Chris
И вернитесь к значению prototype
.
delete b.name;
console.log(b.hasOwnProperty("name")); // false
console.log(b.name); // George
Это мощная часть прототипического наследования.
В другом шаблоне:
function A() {
this.name = "George";
}
Переменная this.name
объявляется снова с каждым новым экземпляром.
Имеет смысл иметь методы как функции, объявленные на прототипе. Вместо того, чтобы определение функции повторно декларировалось в каждом экземпляре, все экземпляры могут использовать одну функцию.
В терминах переменных, а не в функциях, прототип можно использовать для значений по умолчанию в случае, если экземпляр не установил собственное значение.
Код в скрипке
Ответ 2
Значение, сохраненное в прототипе, предоставляет значение по умолчанию для этого свойства.
Если вы впоследствии напишите значение этому свойству, экземпляр получит это новое значение, скрывая значение, которое на прототипе будет оставлено без изменений.
В контексте кода, который вы добавили к вопросу:
MyCircle.prototype.radius;
ничего не делает. Это no-op - он пытается прочитать это свойство и затем отбрасывает результат.
Ответ 3
Да, я согласен, что прототип может использоваться для значений свойств по умолчанию (переменных). Функция конструктора не должна объявлять свойство; это может быть сделано условно.
function Person( name, age ) {
this.name = name;
if ( age ) {
this.age = age;
}
}
Person.prototype.sayHello = function() {
console.log( 'My name is ' + this.name + '.' );
};
Person.prototype.sayAge = function() {
if ( this.age ) {
console.log( 'I am ' + this.age + ' yrs old!' );
} else {
console.log( 'I do not know my age!' );
}
};
Person.prototype.age = 0.7;
//-----------
var person = new Person( 'Lucy' );
console.log( 'person.name', person.name ); // Lucy
console.log( 'person.age', person.age ); // 0.7
person.sayAge(); // I am 0.7 yrs old!
Посмотрите, как Lucy age
условно объявлена и инициализирована.
Ответ 4
Другие ответы уже объяснили разницу между свойствами прототипа vs экземпляра.
Но просто чтобы добавить к ответу, позвольте сломать фрагмент кода:
(function(){ // <------- 1
// constructor
function MyCircle(radius){ // <------- 2
this.radius = radius; // <------- 2.1
}
MyCircle.prototype.radius; // <------- 3
this.area = function(){ // <------- 4
return 3.14*this.radius*this.radius;
};
window.MyCircle = MyCircle; // <------- 5
}());
- Создание
IIFE
, который выступает в качестве контейнера области для внутреннего кода
- Объявление функции с именем
MyCircle
с использованием шаблона конструктора (но обратите внимание, что оно никогда не получается "построено", поэтому, вероятно, следует избавиться от заглавной буквы, поскольку оно вводит в заблуждение)
- при вызове создает свойство экземпляра
radius
для вызываемого объекта
- Попытка получить доступ к свойству
radius
в MyCircle
функции prototype
, которая не существует, поэтому оценивается как undefined
- Создание свойства экземпляра
area
для объекта глобального окна и назначение ему выражения функции
- Создание свойства экземпляра
MyCircle
объекта window
и назначение ему функции MyCircle
Резюме: Кажется, что он создает свойства area
и MyCircle
для глобального объекта window
, а при вызове MyCircle
он создает дополнительное свойство radius
.
Использование: MyCircle следует вызывать перед областью, так как область использует MyCircle, инициализируя радиус:
window.MyCircle(10);
window.area(); // evaluates to 314