Изменяется не прототип, а объект config
, который вы поместили в прототип. Это правильное поведение, объекты, на которые ссылается прототип, не копируются при создании нового экземпляра. f1.config === f2.config
, они указывают на тот же объект.
Способ работы цепочки прототипов для операций get
таков:
- Вы делаете что-то, что ищет свойство объекта. Скажи,
this.config
.
- Сам объект проверяется на наличие свойства с таким именем. Если это так, эта копия свойства используется и возвращается его значение. В вашем случае у объекта нет собственного
config
, поэтому мы переходим к следующему шагу.
- Прототип объекта проверяется на наличие свойства у него . Если это так, эта копия свойства используется и возвращается его значение. В вашем случае это верно, потому что у прототипа есть свойство. Но только для полноты:
- Повторите шаг 3, продолжая вверх (вниз?) Цепочку прототипов по мере необходимости.
- Если свойство не найдено вообще , вернуть
undefined
.
(set
операции работают по-разному; set
операция всегда обновляет или создает свойство объекта, на который она устанавливается, и никогда дальше [вверх?] По цепочке прототипов.)
Так что в вашем случае, поскольку у ваших экземпляров нет свойства config
, мы переходим к прототипу. Поскольку прототип имеет свойство config
, он используется. Значением свойства является ссылка на объект, и поэтому, если вы измените объект (назначив одно из его свойств), он будет изменен, и все, что также использует этот объект, увидит изменение.
Еще один способ взглянуть на это - построить график:
+------+ +------+
| f1 | | f2 |
+------+ +------+
| |
+------+-------+
|
v
+--------------------+ +--------+
| [[Proto]] assigned | | config |
| via `new f` |------>| object |
+--------------------+ +--------+
|
+-------+-------+
| |
V v
+------------+ +------------+
| a property | | b property |
+------------+ +------------+
Еще один способ думать об этом - полностью избавиться от функции и прототипа:
var pseudoProto = {}; // A stand-in for the prototype...
pseudoProto.config = { // ...with `config` on it
a: 'a',
b: 'b'
};
var f1 = {}; // A blank object...
f1.pseudo = pseudoProto; // ...referencing `pseudoProto`
var f2 = {}; // Another blank object...
f2.pseudo = pseudoProto; // ...also referencing `pseudoProto`
f1.pseudo.config.b = "bb"; // Change the `b` property on `config`
console.log(f2.pseudo.config.b); // Logs "bb", of course
По-настоящему, это то, что происходит под прикрытием через new f()
. Вы не можете напрямую получить доступ к свойству экземпляров f1
и f2
, которое указывает на прототип (спецификация называет это свойство [[Proto]]
), но это реальная вещь, и она действительно там. [FYI: последняя версия спецификации ECMAScript позволяет нам делать несколько вещей непосредственно со свойством [[Proto]]
, например создавать необработанные объекты с определенным [[Proto]]
(без использования функции), но все же не дает нам прямой доступ к самой собственности.]
В большинстве случаев вы хотите, чтобы все экземпляры совместно использовали один и тот же объект (например, функциональные объекты!), И поэтому прототип является подходящим местом для ссылок на эти объекты; но если вы хотите, чтобы у каждого экземпляра была собственная копия объекта, вы хотите создать этот объект в функции конструктора. В вашем случае:
var f = function(b){
this.config = {
a: 'a',
b: b
};
}
var f1 = new f('bb');
console.log(f1.config);
var f2 = new f('bbb');
console.log(f2.config);
// Logs
// { a: 'a', b: 'bb' }
// { a: 'a', b: 'bbb' }
(Обратите внимание, что я переместил операторы console.log
, поэтому мы видим результат в конце, а не в промежуточном состоянии.)