Определить функцию, расширить прототип функции, создать два экземпляра, прототип был изменен? - PullRequest
2 голосов
/ 08 марта 2011

Может кто-нибудь объяснить мне, почему результат такой, какой я ожидал. Это сводит меня с ума!

var f = function(b){
    console.log(this.config);
    this.config.b = b;
}
f.prototype.config = {
    a: 'a',
    b: 'b'
};

var f1 = new f('bb');
var f2 = new f('bbb');

// logs
// { a: 'a', b: 'b' }
// { a: 'a', b: 'bb' }

// expected
// { a: 'a', b: 'b' }
// { a: 'a', b: 'b' }

Ответы [ 2 ]

9 голосов
/ 08 марта 2011

Изменяется не прототип, а объект config, который вы поместили в прототип. Это правильное поведение, объекты, на которые ссылается прототип, не копируются при создании нового экземпляра. f1.config === f2.config, они указывают на тот же объект.

Способ работы цепочки прототипов для операций get таков:

  1. Вы делаете что-то, что ищет свойство объекта. Скажи, this.config.
  2. Сам объект проверяется на наличие свойства с таким именем. Если это так, эта копия свойства используется и возвращается его значение. В вашем случае у объекта нет собственного config, поэтому мы переходим к следующему шагу.
  3. Прототип объекта проверяется на наличие свойства у него . Если это так, эта копия свойства используется и возвращается его значение. В вашем случае это верно, потому что у прототипа есть свойство. Но только для полноты:
  4. Повторите шаг 3, продолжая вверх (вниз?) Цепочку прототипов по мере необходимости.
  5. Если свойство не найдено вообще , вернуть 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, поэтому мы видим результат в конце, а не в промежуточном состоянии.)

0 голосов
/ 16 сентября 2011

Вот хороший пример сценария, который демонстрирует проблему:

Person = class
  config:
    name: "sin nombre"
  constructor: (config={}) ->
    @config.name = config.name  if config.name?
  getName: -> @config.name

Human = class extends Person

ben = new Human(name: 'Ben')
sonny = new Human()
alert ben.getName()
alert sonny.getName()

и решение:

Person = class
  config:
    name: "sin nombre"
  constructor: (config={}) ->
    @config = {}
    @config[key] = value  for own key, value of Person::config
    @config.name = config.name  if config.name?
  getName: -> @config.name

Human = class extends Person

ben = new Human(name: 'Ben')
sonny = new Human()
alert ben.getName()
alert sonny.getName()
...