JavaScript - уникальное обновление свойств в литералах прототипов объектов - PullRequest
3 голосов
/ 02 февраля 2011

Я только сейчас приступил к созданию экземпляров литералов объектов JavaScript с использованием конструктора прототипов, но меня немного смущают правила наследования / отражения, которые применяются к моему случаю.

По сути, моя проблема заключается в том, что исходный объект, который прототипируется, имеет несколько уровней свойств / значений:

var protoObj = {  
  prop1: {  
    first : 1,
    second : 2,
    third : {  
      a : 'foo',
      b : 'bar',
      c : {  
        'love': true,  
        'babies': false,
        'joy': undefined
      }
    } 
  },  
  prop2 : true       
};

... где все эти свойства, кроме нескольких, остаются неизменными (и должны динамически обновляться) во всех дочерних объектах.

Я могу обновить или создать новые свойства верхнего уровня для дочерних объектов, не влияя на исходный объект-прототип просто отлично:

if (typeof Object.create !== 'function') {
  Object.create = function(o){
    var F = function(){};
    F.prototype = o;
    return new F();
  };
}

var someObj = Object.create(protoObj);

someObj.prop2 = false;   
//protoObj.prop2 remains true

someObj.prop3 = [x,y,z];
//new property, unique to someObj (i.e. protoObj remains untouched)

Если, однако, я хочу установить уникальное значение для глубоко укоренившегося свойства 'joy' в someObj, я не могу сделать это без переопределения самого protoObj или разрыва цепочки делегирования someObj вернуться к своему прототипу:

someObj.prop1.third.c['joy'] = 'found';
//does not *directly* update someObj
//instead ends up setting the value of protoObj's property 'joy' to be 'found'


someObj.prop1 = {
  first : 1,
  second : 2,
  third : {  
    a : 'foo',
    b : 'bar',
    c : {  
      'love': true,  
      'babies': false,
      'joy': 'found'
    }
  }
};
//explicitly sets the above values in someObj.prop1 (including 'joy'), but breaks
//the prototypal link back to protoObj for all data in someObj.prop1 as well

Я подозреваю, что я либо пропускаю невероятно простое решение, либо совсем не повезло, но я был бы очень обязан услышать от вас все об этом, независимо от этого. Заранее спасибо.

Ответы [ 2 ]

2 голосов
/ 02 февраля 2011

Краткий ответ: вам частично не повезло.

Длинный ответ. Когда вы создаете прототип объекта из другого, вы говорите интерпретатору JavaScript, что производный объект «похож» на объект-предок. Когда вы читаете из свойства, интерпретатор знает, что при необходимости он может пройти цепочку прототипов. Так для:

var x = someObj.prop2;

Сначала он смотрит на someObj объект. Есть ли у него свойство с именем prop2? Нет, так иди посмотри на прототип объекта. Есть ли у него свойство с именем prop2? Да, так что верните это значение.

Когда вы устанавливаете свойство, оно устанавливается непосредственно в объекте. Не ходите по прототипу цепи здесь. Итак:

someObj.prop2 = true;

означает, что свойство prop2 создается в someObj и имеет значение true. После этого момента, когда вы прочитаете prop2, вы получите прямое значение и не будете ходить по цепочке прототипов.

Теперь с prop1 происходит то же самое. Прочитайте это от someObj

var x = someObj.prop1;

и вы получите объект prop1 от объекта-прототипа, так как он не существует в someObj.

Но будьте осторожны: это ссылка на свойство propObj. Измените его свойства, и вы замените их на propObj. Если вам нужно внести изменения в prop1 так, чтобы они были видны только в someObj, вам придется также прототипировать этот объект:

var someObj = Object.create(propObj);
someObj.prop1 = Object.create(propObj.prop1);
someObj.prop1.first = 42;

Здесь последняя строка изменит значение first в someObj.prop1, а не значение в propObj. Если вам нужно пойти глубже, прототипировать следующий уровень вниз и так далее, и так далее.

1 голос
/ 02 февраля 2011

Я думаю, что понял.

Прототипирование используется для обмена методами со всеми экземплярами. Похоже, вы пытаетесь использовать прототип, чтобы дать конкретным членам экземпляра производный объект someObj.

Но чтобы понять, почему это плохое решение, вот как работает прототипирование:

Когда JS пытается разрешить (сохранить) ссылку на элемент на объекте, если он не находит этот элемент в объекте, он просматривает свой прототип (и так далее по цепочке прототипов) .

Поэтому, когда вы хотите прочитать someObj.prop1, JS не найдет prop1 в someObj, он ищет в своем прототипе protoObj и найдет protoObj.prop1:

Таким образом, совершенно нормально, что:

someObj.prop1.third.c['joy'] = 'found'

... изменяет прототип, потому что someObj.prop1 разрешается как protoObj.prop1

Незначительная деталь, которую вы игнорируете, состоит в том, что в JS не требуется разрешение члена. Когда ты пишешь что-то вроде:

someObj.stuff.other = 3

JS разрешает someObj.stuff, потому что вы спрашиваете .other .

Но когда вы набираете:

someObj.stuff = 3

JS не разрешает что-либо !! Это только влияет на 3 на вещи члена someObj и вообще не заботится о существовании или несуществовании вещи в someObj или в его прототипе.

Если материал был эффективно в прототипе, новый одноименный член маскирует прототип.

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

function MyObj() {
  this.prop1 = {first: 1, second: 2, third: {a: ...} }
  this.prop2 = true;
}

Очевидно, что у каждого экземпляра есть свои собственные члены, отличные от членов protoObj.

Если вы хотите использовать прототипирование для предоставления некоторых значений по умолчанию и изменяемых значений вашим объектам и иметь возможность «разветвлять» часть этих значений, когда вы изменяете их через экземпляр (своего рода копирование при записи), это просто невозможно в JavaScript.

...