Я читал "Javascript: Good Parts" Дугласа Крокфорда - и, хотя это немного экстремально, я на борту с большим количеством того, что он должен сказать.
Итак, я попытался использовать это в проекте, над которым я работаю, и я заметил проблему при попытке наследовать от объектов, которые являются вложенными. Если я перезаписываю значение вложенного объекта, унаследованного с помощью этого шаблона, он перезаписывает вложенный элемент на всем протяжении цепи прототипа.
Ответ 3
Я изменил примеры, чтобы лучше продемонстрировать, что здесь происходит. 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"
}
Все работает точно так, как вы ожидали.