2012-04-12 4 views
12

Я читал «Javascript: The Good Parts» Дугласа Крокфорда - и, хотя это немного экстремально, я на борту с большим количеством того, что он должен сказать.Prototypal inheritance Crockford - Проблемы с вложенными объектами

В главе 3, он рассматривает объекты и в один момент выкладывает рисунок (также found here) для упрощения & избежать некоторых из путаницы/вопросов, которые приходят с использованием встроенного в «новой» ключевое слово.

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

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

Пример Крокфорда похож на flatObj в следующем примере, который хорошо работает. Поведение, однако, не согласуется с вложенными объектами:

var flatObj = { 
    firstname: "John", 
    lastname: "Doe", 
    age: 23 
} 
var person1 = Object.create(flatObj); 

var nestObj = { 
    sex: "female", 
    info: { 
     firstname: "Jane", 
     lastname: "Dough", 
     age: 32 
    } 
} 
var person2 = Object.create(nestObj); 

var nestObj2 = { 
    sex: "male", 
    info: { 
     firstname: "Arnold", 
     lastname: "Schwarzenneger", 
     age: 61 
    } 
} 
var person3 = { 
    sex: "male" 
} 
person3.info = Object.create(nestObj2.info); 

// now change the objects: 
person1.age = 69; 
person2.info.age = 96; 
person3.info.age = 0; 

// prototypes should not have changed: 
flatObj.age // 23 
nestObj.info.age // 96 ??? 
nestObj2.info.age // 61 

// now delete properties: 
delete person1.age; 
delete person2.info.age; 
delete person3.info.age; 

// prototypes should not have changed: 
flatObj.age // 23 
nestObj.info.age // undefined ??? 
nestObj2.info.age // 61 

(также и на fiddle)

Я делаю что-то неправильно, или это ограничение этой модели?

+0

related: [JavaScript Object.create - наследование вложенных свойств] (http://stackoverflow.com/q/3191103/1048572) – Bergi

ответ

10

Нет несоответствия. Просто не думайте о вложенных объектах: a direct Свойство объекта всегда либо в его прототипе, либо в собственном свойстве. Не имеет значения значение свойства примитив или объект.

Итак, когда вы делаете

var parent = { 
    x: {a:0} 
}; 
var child = Object.create(parent); 

child.x будет ссылки на один и тот же объект, как parent.x - это один {a:0} объект. И когда вы измените его свойство:

var prop_val = child.x; // == parent.x 
prop_val.a = 1; 

оба будут затронуты. Для того, чтобы изменить свойство «вложенной» независимо друг от друга, вы сначала должны создать независимый объект:

child.x = {a:0}; 
child.x.a = 1; 
parent.x.a; // still 0 

Что вы можете сделать, это

child.x = Object.create(parent.x); 
child.x.a = 1; 
delete child.x.a; // (child.x).a == 0, because child.x inherits from parent.x 
delete child.x; // (child).x.a == 0, because child inherits from parent 

означает, что они не являются абсолютно независимыми, - но до сих пор две разные объекты.

+0

Это немного проясняет ситуацию - и теперь я понимаю эту проблему. Спасибо :) потому что все объекты являются ссылками ... Duh. Еще менее удобно. Ну что ж. – 1nfiniti

+0

* «Прямое свойство объекта всегда либо на его прототипе, либо на собственном свойстве» * - Мне прямое свойство означает свойство объекта, а те, что в его прототипной цепочке, можно назвать * косвенным *. Можете ли вы объяснить, что именно вы подразумеваете под прямым свойством, или тем, что является * косвенным свойством.? –

+0

@TJ Я думаю, что я использовал термин «прямой» для обозначения противоположности «вложенных» здесь. Один уровень в цепочке свойств, так сказать. – Bergi

1

Я думаю, что это происходит в том, что при создании person2, в sex и info свойства него относятся к тем, в nestObj. Когда вы ссылаетесь на person2.info, поскольку person2 не переопределяет свойство info, оно переходит к прототипу и изменяет его.

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

Я тоже читаю книгу (медленно), поэтому я сочувствую вам. :)

1

Я изменил примеры, чтобы лучше продемонстрировать, что здесь происходит.Demo

Сначала мы создаем объект с тремя свойствами; Число, строка и объект с одним свойством со строковым значением.

Затем мы создаем второй объект из первого, используя Object.create();

var obj1 = { 
    num : 1, 
    str : 'foo', 
    obj : { less: 'more' } 
}; 
var obj2 = Object.create(obj1); 

console.log('[1] obj1:', obj1); 
console.log('[1] obj2:', obj2); 
"[1] obj1:" 
[object Object] { 
    num: 1, 
    obj: [object Object] { 
    less: "more" 
    }, 
    str: "foo" 
} 
"[1] obj2:" 
[object Object] { 
    num: 1, 
    obj: [object Object] { 
    less: "more" 
    }, 
    str: "foo" 
} 

Выглядит хорошо, верно? У нас есть наш первый объект и второй скопированный объект.

Не так быстро; Посмотрим, что произойдет, когда мы изменим некоторые значения для первого объекта.

obj1.num = 3; 
obj1.str = 'bar'; 
obj1.obj.less = 'less'; 

console.log('[2] obj1:', obj1); 
console.log('[2] obj2:', obj2); 
"[2] obj1:" 
[object Object] { 
    num: 3, 
    obj: [object Object] { 
    less: "less" 
    }, 
    str: "bar" 
} 
"[2] obj2:" 
[object Object] { 
    num: 3, 
    obj: [object Object] { 
    less: "less" 
    }, 
    str: "bar" 
} 

Теперь снова наш первый объект, с изменениями, и копия этого объекта. Что тут происходит?

Давайте проверим, имеют ли объекты свои свойства.

for(var prop in obj1) console.log('[3] obj1.hasOwnProperty(' + prop + '): ' + obj1.hasOwnProperty(prop)); 
for(var prop in obj2) console.log('[3] obj2.hasOwnProperty(' + prop + '): ' + obj2.hasOwnProperty(prop)); 
"[3] obj1.hasOwnProperty(num): true" 
"[3] obj1.hasOwnProperty(str): true" 
"[3] obj1.hasOwnProperty(obj): true" 
"[3] obj2.hasOwnProperty(num): false" 
"[3] obj2.hasOwnProperty(str): false" 
"[3] obj2.hasOwnProperty(obj): false" 

obj1 обладает всеми своими свойствами, так же, как мы определили, но obj2 не делает.

Что происходит, когда мы меняем некоторые свойства obj2?

obj2.num = 1; 
obj2.str = 'baz'; 
obj2.obj.less = 'more'; 

console.log('[4] obj1:', obj1); 
console.log('[4] obj2:', obj2); 
for(var prop in obj1) console.log('[4] obj1.hasOwnProperty(' + prop + '): ' + obj1.hasOwnProperty(prop)); 
for(var prop in obj2) console.log('[4] obj2.hasOwnProperty(' + prop + '): ' + obj2.hasOwnProperty(prop)); 
"[4] obj1:" 
[object Object] { 
    num: 3, 
    obj: [object Object] { 
    less: "more" 
    }, 
    str: "bar" 
} 
"[4] obj2:" 
[object Object] { 
    num: 1, 
    obj: [object Object] { 
    less: "more" 
    }, 
    str: "baz" 
} 
"[4] obj1.hasOwnProperty(num): true" 
"[4] obj1.hasOwnProperty(str): true" 
"[4] obj1.hasOwnProperty(obj): true" 
"[4] obj2.hasOwnProperty(num): true" 
"[4] obj2.hasOwnProperty(str): true" 
"[4] obj2.hasOwnProperty(obj): false" 

Так, num и str изменился на obj2, а не на obj1 так же, как мы хотели, но obj1.obj.less изменилось, когда он не должен иметь.

Из проверки hasOwnProperty() мы можем видеть, что, хотя мы изменили obj2.obj.less, мы не задали obj2.obj. Это означает, что мы все еще имеем в виду obj1.obj.less.

Создайте объект с obj1.obj и назначьте его obj2.obj и посмотрите, дает ли это нам то, что мы ищем.

obj2.obj = Object.create(obj1.obj); 

console.log('[5] obj1:', obj1); 
console.log('[5] obj2:', obj2); 
for(var prop in obj1) console.log('[5] obj1.hasOwnProperty(' + prop + '): ' + obj1.hasOwnProperty(prop)); 
for(var prop in obj2) console.log('[5] obj2.hasOwnProperty(' + prop + '): ' + obj2.hasOwnProperty(prop)); 
"[5] obj1:" 
[object Object] { 
    num: 3, 
    obj: [object Object] { 
    less: "more" 
    }, 
    str: "bar" 
} 
"[5] obj2:" 
[object Object] { 
    num: 1, 
    obj: [object Object] { 
    less: "more" 
    }, 
    str: "baz" 
} 
"[5] obj1.hasOwnProperty(num): true" 
"[5] obj1.hasOwnProperty(str): true" 
"[5] obj1.hasOwnProperty(obj): true" 
"[5] obj2.hasOwnProperty(num): true" 
"[5] obj2.hasOwnProperty(str): true" 
"[5] obj2.hasOwnProperty(obj): true" 

Это хорошо, теперь obj2 имеет свою собственную obj собственность. Посмотрим, что произойдет, когда мы изменим сейчас obj2.obj.less.

obj2.obj.less = 'less'; 

console.log('[6] obj1:', obj1); 
console.log('[6] obj2:', obj2); 
"[6] obj1:" 
[object Object] { 
    num: 3, 
    obj: [object Object] { 
    less: "more" 
    }, 
    str: "bar" 
} 
"[6] obj2:" 
[object Object] { 
    num: 1, 
    obj: [object Object] { 
    less: "less" 
    }, 
    str: "baz" 
} 

Так что все это говорит нам о том, что, если имущество еще не было изменено на созданном объекте, любые get запросы к созданному объекту для этого свойства будет перенаправлен к исходному объекту.

set запроса obj2.obj.less = 'more' от предыдущего кодового блока первым требует get запроса на obj2.obj, который не существует в obj2 в этой точке, так что он направляет к obj1.obj и в своей очереди obj1.obj.less.

потом, наконец, когда мы вновь читали obj2, мы до сих пор не установлены obj2.obj так что get запроса будет направлен obj1.obj и вернуть настройки, которые мы ранее изменились, вызывая эффект, изменяя свойства второго объекта возражать ребенок, похоже, меняет оба, но на самом деле это только изменение первого.

Вы можете использовать эту функцию, чтобы вернуть новый объект, полностью отделенный от оригинала рекурсивно.

Demo

var obj1 = { 
    num : 1, 
    str : 'foo', 
    obj : { less: 'more' } 
}; 
var obj2 = separateObject(obj1); 

function separateObject(obj1) { 

    var obj2 = Object.create(Object.getPrototypeOf(obj1)); 
    for(var prop in obj1) { 
     if(typeof obj1[prop] === "object") 
      obj2[prop] = separateObject(obj1[prop]); 
     else 
      obj2[prop] = obj1[prop]; 
    } 

    return obj2; 
} 

console.log('[1] obj1:', obj1); 
console.log('[1] obj2:', obj2); 
for(var prop in obj1) console.log('[1] obj1.hasOwnProperty(' + prop + '): ' + obj1.hasOwnProperty(prop)); 
for(var prop in obj2) console.log('[1] obj2.hasOwnProperty(' + prop + '): ' + obj2.hasOwnProperty(prop)); 
"[1] obj1:" 
[object Object] { 
    num: 1, 
    obj: [object Object] { 
    less: "more" 
    }, 
    str: "foo" 
} 
"[1] obj2:" 
[object Object] { 
    num: 1, 
    obj: [object Object] { 
    less: "more" 
    }, 
    str: "foo" 
} 
"[1] obj1.hasOwnProperty(num): true" 
"[1] obj1.hasOwnProperty(str): true" 
"[1] obj1.hasOwnProperty(obj): true" 
"[1] obj2.hasOwnProperty(num): true" 
"[1] obj2.hasOwnProperty(str): true" 
"[1] obj2.hasOwnProperty(obj): true" 

Давайте посмотрим, что происходит, когда мы изменили некоторые переменные в настоящее время.

obj1.num = 3; 
obj1.str = 'bar'; 
obj1.obj.less = 'less'; 

console.log('[2] obj1:', obj1); 
console.log('[2] obj2:', obj2); 
"[2] obj1:" 
[object Object] { 
    num: 3, 
    obj: [object Object] { 
    less: "less" 
    }, 
    str: "bar" 
} 
"[2] obj2:" 
[object Object] { 
    num: 1, 
    obj: [object Object] { 
    less: "more" 
    }, 
    str: "foo" 
} 

Все работает именно так, как вы ожидали.

+0

Какая консоль имеет такие объекты console.log, не отображая структуры наследования? – Bergi

+0

Для 'separateObject' я бы рекомендовал' Object.create (Object.getPrototypeOf (obj1)) ' – Bergi

+0

Я скопировал вывод из консоли JSBin. Я просто попробовал 'Object.getPrototypeOf (obj1)', но он возвратил пустой объект. Я что-то упускаю? I –

Смежные вопросы